• passes: 378
  • failures: 0
  • duration: 3.2s
  • Diagnostics

    • script sets up a "respond" and a "hear" listener

      • robot.respond called once to set up listener1ms

        return pretend.robot.respond.should.have.calledOnce;
      • registers a respond listener with RegExp and function1ms

        return pretend.robot.respond.getCall(0).should.have.calledWithMatch(sinon.match.regexp, sinon.match.func);
      • robot.hear called twice (by respond then directly)0ms

        return pretend.robot.hear.should.have.calledTwice;
      • registers a hear listener with RegExp and callback (no options)0ms

        return pretend.robot.hear.getCall(1).should.have.calledWithMatch(sinon.match.regexp, sinon.match.func);
      • robbot has two listeners0ms

        return pretend.robot.listeners.length.should.equal(2);
    • bot responds to a matching message

      • bot creates response0ms

        return pretend.responses.listen.length.should.equal(1);
      • bot calls listener callback with response0ms

        return this.cb.should.have.calledWithMatch(sinon.match(matchRes));
    • bot hears a matching message

      • bot creates response0ms

        return pretend.responses.listen.length.should.equal(1);
      • bot calls listener callback with response0ms

        return this.cb.should.have.calledWithMatch(sinon.match(matchRes));
    • bot responds to its alias

      • calls callback with response8ms

        return co(function*() {
          var cb;
          pretend.start({
            alias: 'buddy'
          });
          cb = sinon.spy(pretend.robot.listeners[0], 'callback');
          yield pretend.user('jimbo').send('buddy which version');
          return cb.should.have.calledWithMatch(sinon.match(matchRes));
        });
    • user asks for version number

      • replies to tester with a version number2ms

        return co(function*() {
          yield pretend.user('jimbo').send('hubot which version are you on?');
          return pretend.messages[1][1].should.match(/jimbo .*\d+.\d+.\d+/);
        });
    • user asks different ways if Hubot is listening

      • replies to each confirming Hubot listening4ms

        return co(function*() {
          yield pretend.user('jimbo').send('Are any Hubots listening?');
          yield pretend.user('jimbo').send('Is there a bot listening?');
          return pretend.messages[1].should.eql(pretend.messages[3]);
        });
  • Base

    • .constructor

      • with name, robot and options and key

        • stores the robot0ms

          return this.base.robot.should.eql(pretend.robot);
        • calls configure with options1ms

          return this.base.configure.should.have.calledWith({
            test: 'testing'
          });
        • sets key attribute0ms

          return this.base.key.should.equal('basey-mcbase');
      • without robot

        • runs error handler0ms

          return Base.prototype.error.should.have.calledOnce;
      • without name

        • runs error handler0ms

          return Base.prototype.error.should.have.calledOnce;
    • .log

      • writes log on the given level1ms

        var base;
        base = new Base('test', pretend.robot);
        base.id = 'test111';
        base.log.debug('This is some debug');
        base.log.info('This is info');
        base.log.warning('This is a warning');
        base.log.error('This is an error');
        return pretend.logs.slice(-4).should.eql([['debug', 'This is some debug (id: test111)'], ['info', 'This is info (id: test111)'], ['warning', 'This is a warning (id: test111)'], ['error', 'This is an error (id: test111)']]);
      • appends the key if instance has one0ms

        var base;
        base = new Base('test', pretend.robot, 'super-test');
        base.id = 'test111';
        base.log.debug('This is some debug');
        return pretend.logs.slice(-1).should.eql([['debug', 'This is some debug (id: test111, key: super-test)']]);
    • .error

      • with an error

        • logs an error0ms

          return this.errLog[0].should.equal('error');
        • emits the error through robot0ms

          return pretend.robot.emit.should.have.calledWith('error', this.err);
        • threw error0ms

          return this.base.error.should["throw"];
      • with error context string

        • logs an error with the module instance ID and context string0ms

          return this.errLog[1].should.match(new RegExp(this.base.id + ".*something broke"));
        • emits an error through robot0ms

          return pretend.robot.emit.should.have.calledWith('error');
        • threw error0ms

          return this.base.error.should["throw"];
      • using inherited method for error

        • calls inherited method0ms

          return Base.prototype.error.should.have.calledWith('Throw me an error');
        • threw0ms

          return this.module.error.should["throw"];
    • .configure

      • saves new options0ms

        var base;
        base = new Base('module', pretend.robot);
        base.configure({
          foo: true
        });
        return base.config.foo.should.be["true"];
      • overrides existing config0ms

        var base;
        base = new Base('module', pretend.robot, {
          setting: true
        });
        base.configure({
          setting: false
        });
        return base.config.setting.should.be["false"];
      • throws when not given options1ms

        var base;
        base = new Base('module', pretend.robot);
        try {
          base.configure('not an object');
        } catch (error) {}
        return base.configure.should["throw"];
    • .defaults

      • sets config if not set0ms

        this.base = new Base('module', pretend.robot);
        this.base.defaults({
          setting: true
        });
        return this.base.config.should.eql({
          setting: true
        });
      • does not change config if already set0ms

        this.base = new Base('module', pretend.robot, {
          setting: true
        });
        this.base.defaults({
          setting: false
        });
        return this.base.config.should.eql({
          setting: true
        });
    • .emit

      • emits event via the robot with instance as first arg0ms

        this.base = new Base('module', pretend.robot);
        this.eventSpy = sinon.spy();
        pretend.robot.on('mockEvent', this.eventSpy);
        this.base.emit('mockEvent', {
          foo: 'bar'
        });
        return this.eventSpy.should.have.calledWith(this.base, {
          foo: 'bar'
        });
    • .on

      • relays events from robot to instance0ms

        this.base = new Base('module', pretend.robot);
        this.mockEvent = sinon.spy();
        this.base.on('mockEvent', this.mockEvent);
        pretend.robot.emit('mockEvent', this.base, {
          foo: 'bar'
        });
        return this.mockEvent.should.have.calledWith({
          foo: 'bar'
        });
  • Path

    • constructor

      • with branches

        • calls .addBranch1ms

          var path;
          sinon.spy(Path.prototype, 'addBranch');
          path = new Path(pretend.robot, [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']]);
          path.addBranch.args.should.eql([[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']]);
          return Path.prototype.addBranch.restore();
        • is not closed0ms

          var path;
          path = new Path(pretend.robot, [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']]);
          return path.closed.should.be["false"];
      • with a single branch

        • calls .addBranch0ms

          var path;
          sinon.spy(Path.prototype, 'addBranch');
          path = new Path(pretend.robot, [/ok/, 'OK, ok!']);
          path.addBranch.args.should.eql([[/ok/, 'OK, ok!']]);
          return Path.prototype.addBranch.restore();
        • is not closed0ms

          var path;
          path = new Path(pretend.robot, [/ok/, 'OK, ok!']);
          return path.closed.should.be["false"];
      • with undefined branches and options

        • does not call .addBranch0ms

          var path;
          sinon.spy(Path.prototype, 'addBranch');
          path = new Path(pretend.robot);
          return Path.prototype.addBranch.restore();
        • stays closed0ms

          var path;
          path = new Path(pretend.robot);
          return path.closed.should.be["true"];
      • with bad arguments for branch

        • throws1ms

          try {
            this.path = new Path(pretend.robot, 'breakme.jpg');
          } catch (error) {}
          return Path.constructor.should["throw"];
    • .addBranch

      • creates branch object1ms

        var path;
        path = new Path(pretend.robot);
        path.addBranch(/.*/, 'foo', function() {});
        return path.branches[0].should.be.an('object');
      • branch has valid regex0ms

        var path;
        path = new Path(pretend.robot);
        path.addBranch(/.*/, 'foo', function() {});
        return path.branches[0].regex.should.be["instanceof"](RegExp);
      • accepts a string that can be cast as RegExp1ms

        var path;
        path = new Path(pretend.robot);
        path.addBranch('/.*/ig', 'foo', function() {});
        return path.branches[0].regex.should.be["instanceof"](RegExp);
      • calls getHandler with strings and callback1ms

        var callback, path;
        path = new Path(pretend.robot);
        callback = function() {};
        sinon.stub(path, 'getHandler');
        path.addBranch(/.*/, 'foo', callback);
        return path.getHandler.should.have.calledWithExactly('foo', callback);
      • calls getHandler with just stirngs1ms

        var path;
        path = new Path(pretend.robot);
        sinon.stub(path, 'getHandler');
        path.addBranch(/.*/, ['foo', 'bar']);
        return path.getHandler.should.have.calledWithExactly(['foo', 'bar'], void 0);
      • calls getHandler with just callback0ms

        var callback, path;
        path = new Path(pretend.robot);
        callback = function() {};
        sinon.stub(path, 'getHandler');
        path.addBranch(/.*/, callback);
        return path.getHandler.should.have.calledWithExactly(void 0, callback);
      • branch stores handler1ms

        var lasthandler, path;
        path = new Path(pretend.robot);
        sinon.spy(path, 'getHandler');
        path.addBranch(/.*/, 'foo', function() {});
        lasthandler = path.getHandler.returnValues[0];
        return path.branches[0].handler.should.eql(lasthandler);
      • opens path1ms

        var path;
        path = new Path(pretend.robot);
        path.addBranch(/.*/, 'foo', function() {});
        return path.closed.should.be["false"];
      • throws with invalid regex1ms

        var path;
        path = new Path(pretend.robot);
        try {
          path.addBranch('derp');
        } catch (error) {}
        return path.addBranch.should["throw"];
      • throws with insufficient args0ms

        var path;
        path = new Path(pretend.robot);
        try {
          path.addBranch(/.*/);
        } catch (error) {}
        return path.addBranch.should["throw"];
      • throws with invalid message0ms

        var path;
        path = new Path(pretend.robot);
        try {
          path.addBranch(/.*/, function() {});
        } catch (error) {}
        return path.addBranch.should["throw"];
      • throws with invalid callback0ms

        var path;
        path = new Path(pretend.robot);
        try {
          path.addBranch(/.*/, 'foo', 'bar');
        } catch (error) {}
        return path.addBranch.should["throw"];
    • .getHandler

      • returns a function0ms

        var callback, handler, path;
        path = new Path(pretend.robot);
        callback = sinon.spy();
        handler = path.getHandler(['foo', 'bar'], callback);
        return handler.should.be.a('function');
      • when handler called with response

        • calls the callback with the response1ms

          var callback, handler, mockRes, path;
          path = new Path(pretend.robot);
          callback = sinon.spy();
          handler = path.getHandler(['foo', 'bar'], callback);
          mockRes = {
            reply: sinon.spy()
          };
          handler(mockRes);
          return callback.should.have.calledWithExactly(mockRes);
        • sends strings with dialogue if it has one0ms

          var handler, mockRes, path;
          path = new Path(pretend.robot);
          handler = path.getHandler(['foo', 'bar']);
          mockRes = {
            reply: sinon.spy(),
            dialogue: {
              send: sinon.spy()
            }
          };
          handler(mockRes);
          return mockRes.dialogue.send.should.have.calledWith('foo', 'bar');
        • uses response reply if there is no dialogue0ms

          var handler, mockRes, path;
          path = new Path(pretend.robot);
          handler = path.getHandler(['foo', 'bar']);
          mockRes = {
            reply: sinon.spy()
          };
          handler(mockRes);
          return mockRes.reply.should.have.calledWith('foo', 'bar');
        • returns promise resolving with send results0ms

          return co(function*() {
            var handler, handlerSpy, mockRes, path;
            path = new Path(pretend.robot);
            handler = path.getHandler(['foo'], function() {
              return {
                bar: 'baz'
              };
            });
            mockRes = {
              reply: sinon.spy()
            };
            handlerSpy = sinon.spy(handler);
            yield handler(mockRes);
            return handlerSpy.returned(sinon.match(resMatch));
          });
        • returns promise also merged with callback results0ms

          return co(function*() {
            var handler, handlerSpy, mockRes, path;
            path = new Path(pretend.robot);
            handler = path.getHandler(['foo'], function() {
              return {
                bar: 'baz'
              };
            });
            mockRes = {
              reply: sinon.spy()
            };
            handlerSpy = sinon.spy(handler);
            yield handler(mockRes);
            return handlerSpy.returned(sinon.match({
              bar: 'baz'
            }));
          });
    • .match

      • with string matching branch regex

        • updates match in response object4ms

          return co((function(_this) {
            return function*() {
              var expectedMatch;
              yield pretend.user('sam').send('door 1');
              yield _this.path.match(pretend.lastListen());
              expectedMatch = 'door 1'.match(_this.path.branches[0].regex);
              return pretend.lastListen().match.should.eql(expectedMatch);
            };
          })(this));
        • closes the path4ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door 2');
              yield _this.path.match(pretend.lastListen());
              return _this.path.closed.should.be["true"];
            };
          })(this));
        • calls the handler for matching branch with res2ms

          return co((function(_this) {
            return function*() {
              var res;
              _this.path.branches[1].handler = sinon.stub();
              yield pretend.user('sam').send('door 2');
              res = pretend.lastListen();
              yield _this.path.match(res);
              return _this.path.branches[1].handler.should.have.calledWithExactly(res);
            };
          })(this));
        • emits match with res2ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door 2');
              yield _this.path.match(pretend.lastListen());
              return _this.match.should.have.calledWith(sinon.match(resMatch));
            };
          })(this));
      • with string matching multiple branches

        • updates match in response object1ms

          return co((function(_this) {
            return function*() {
              var expectedMatch;
              yield pretend.user('sam').send('door 1 and door 2');
              yield _this.path.match(pretend.lastListen());
              expectedMatch = 'door 1 and door 2'.match(_this.path.branches[0].regex);
              return pretend.lastListen().match.should.eql(expectedMatch);
            };
          })(this));
        • calls the first matching branch handler2ms

          return co((function(_this) {
            return function*() {
              var result;
              yield pretend.user('sam').send('door 1 and door 2');
              result = (yield _this.path.match(pretend.lastListen()));
              return result.should.have.property('winner', false);
            };
          })(this));
        • closes the path1ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door 1 and door 2');
              yield _this.path.match(pretend.lastListen());
              return _this.path.closed.should.be["true"];
            };
          })(this));
      • with mismatching string and no catch

        • returns undefined2ms

          return co((function(_this) {
            return function*() {
              var result;
              yield pretend.user('sam').send('door X');
              result = (yield _this.path.match(pretend.lastListen()));
              return chai.expect(result).to.be.undefined;
            };
          })(this));
        • updates match to null in response object2ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door X');
              yield _this.path.match(pretend.lastListen());
              return chai.expect(pretend.lastListen().match).to.be["null"];
            };
          })(this));
        • path stays open8ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door X');
              yield _this.path.match(pretend.lastListen());
              return _this.path.closed.should.be["false"];
            };
          })(this));
        • emits mismatch with res2ms

          return co((function(_this) {
            return function*() {
              yield pretend.user('sam').send('door X');
              yield _this.path.match(pretend.lastListen());
              return _this.mismatch.should.have.calledWith(sinon.match(resMatch));
            };
          })(this));
      • with mismatching string and catch message

        • returns the response from send2ms

          return co((function(_this) {
            return function*() {
              var result;
              _this.path.configure({
                catchMessage: 'no, wrong door'
              });
              yield pretend.user('sam').send('door X');
              result = (yield _this.path.match(pretend.lastListen()));
              return result.strings.should.eql(['no, wrong door']);
            };
          })(this));
        • emits catch with res3ms

          return co((function(_this) {
            return function*() {
              _this.path.configure({
                catchMessage: 'no, wrong door'
              });
              yield pretend.user('sam').send('door X');
              yield _this.path.match(pretend.lastListen());
              return _this["catch"].should.have.calledWith(sinon.match(resMatch));
            };
          })(this));
      • with mismatching string and catch callback

        • returns the result of the callback3ms

          return co((function(_this) {
            return function*() {
              var result;
              _this.path.configure({
                catchCallback: function() {
                  return {
                    other: 'door fail'
                  };
                }
              });
              yield pretend.user('sam').send('door X');
              result = (yield _this.path.match(pretend.lastListen()));
              return result.should.have.property('other', 'door fail');
            };
          })(this));
        • emits catch with res2ms

          return co((function(_this) {
            return function*() {
              _this.path.configure({
                catchMessage: 'no, wrong door'
              });
              yield pretend.user('sam').send('door X');
              yield _this.path.match(pretend.lastListen());
              return _this["catch"].should.have.calledWith(sinon.match(resMatch));
            };
          })(this));
  • Dialogue

    • constructor

      • has null path1ms

        var dialogue;
        dialogue = new Dialogue(testRes);
        return should.equal(dialogue.path, null);
      • is not ended0ms

        var dialogue;
        dialogue = new Dialogue(testRes);
        return dialogue.ended.should.be["false"];
      • uses timeout from env0ms

        var dialogue;
        process.env.DIALOGUE_TIMEOUT = 500;
        dialogue = new Dialogue(testRes);
        dialogue.config.timeout.should.equal(500);
        return delete process.env.DIALOGUE_TIMEOUT;
    • .end

      • before messages received

        • emits end with initial response1ms

          var dialogue, end;
          dialogue = new Dialogue(testRes);
          end = sinon.spy();
          dialogue.on('end', end);
          dialogue.end();
          return end.should.have.calledWith(testRes);
        • sets ended to true0ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.end();
          return dialogue.ended.should.be["true"];
        • returns true1ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          return dialogue.end().should.be["true"];
      • after messages received

        • emits end with latest response (containing dialogue)0ms

          var dialogue, end;
          dialogue = new Dialogue(testRes);
          end = sinon.spy();
          dialogue.on('end', end);
          dialogue.receive(testRes);
          dialogue.end();
          return end.should.have.calledWith(sinon.match(matchRes));
      • when timeout is running

        • clears the timeout1ms

          var dialogue;
          dialogue = new Dialogue(testRes, {
            timeout: 10
          });
          dialogue.onTimeout = sinon.spy();
          dialogue.startTimeout();
          dialogue.end();
          clock.tick(20);
          dialogue.clearTimeout.should.have.calledOnce;
          return dialogue.onTimeout.should.not.have.called;
      • when already ended

        • returns false0ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.end();
          return dialogue.end().should.be["false"];
        • should only emit end event once0ms

          var dialogue, end;
          dialogue = new Dialogue(testRes);
          end = sinon.spy();
          dialogue.on('end', end);
          dialogue.end();
          dialogue.end();
          return end.should.have.calledOnce;
    • .send

      • with config.sendReplies set to false

        • sends to the room from original res0ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            yield dialogue.send('test');
            return pretend.messages.pop().should.eql(['testing', 'hubot', 'test']);
          });
        • emits send event with new response (containing dialogue)1ms

          return co(function*() {
            var dialogue, sendSpy;
            dialogue = new Dialogue(testRes);
            sendSpy = sinon.spy();
            dialogue.on('send', sendSpy);
            yield dialogue.send('test');
            return sendSpy.should.have.calledWith(sinon.match(matchRes));
          });
        • also emits with strings, methdod and original res0ms

          return co(function*() {
            var dialogue, sendSpy;
            dialogue = new Dialogue(testRes);
            sendSpy = sinon.spy();
            dialogue.on('send', sendSpy);
            yield dialogue.send('test');
            return sendSpy.lastCall.args[1].should.eql({
              strings: ['test'],
              method: 'send',
              received: testRes
            });
          });
      • with config.sendReplies set to true

        • sends to room from original res, responding to the @user0ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes, {
              sendReplies: true
            });
            yield dialogue.send('test');
            return pretend.messages.pop().should.eql(['testing', 'hubot', '@tester test']);
          });
    • .onTimeout

      • default method

        • sends timeout message to room0ms

          function* () {
                    var dialogue, nextMessage;
                    nextMessage = pretend.observer.next();
                    dialogue = new Dialogue(testRes, {
                      timeout: 10
                    });
                    dialogue.startTimeout();
                    clock.tick(20);
                    yield nextMessage;
                    return pretend.messages.pop().should.eql(['testing', 'hubot', dialogue.config.timeoutText]);
                  }
      • method override (as argument)

        • calls the override method0ms

          var dialogue, timeout;
          dialogue = new Dialogue(testRes, {
            timeout: 10
          });
          timeout = sinon.spy();
          dialogue.onTimeout(timeout);
          dialogue.startTimeout();
          clock.tick(20);
          return dialogue.onTimeout.should.have.calledOnce;
        • does not send the default timeout message0ms

          var dialogue, timeout;
          dialogue = new Dialogue(testRes, {
            timeout: 10
          });
          timeout = sinon.spy();
          dialogue.onTimeout(timeout);
          dialogue.startTimeout();
          clock.tick(20);
          return dialogue.send.should.not.have.called;
      • method override (by assignment)

        • calls the override method1ms

          var dialogue;
          dialogue = new Dialogue(testRes, {
            timeout: 10
          });
          dialogue.onTimeout = sinon.spy();
          dialogue.startTimeout();
          clock.tick(20);
          return dialogue.onTimeout.should.have.calledOnce;
      • method override with invalid function

        • throws exception0ms

          var dialogue, override;
          dialogue = new Dialogue(testRes, {
            timeout: 10
          });
          dialogue.onTimeout(function() {
            throw new Error("Test exception");
          });
          override = sinon.spy(dialogue, 'onTimeout');
          dialogue.startTimeout();
          try {
            clock.tick(20);
          } catch (error) {}
          return override.should["throw"];
    • .startTimeout

      • emits timeout event1ms

        var dialogue, timeoutEvent, timeoutMethod;
        dialogue = new Dialogue(testRes, {
          timeout: 10
        });
        timeoutMethod = sinon.spy();
        dialogue.onTimeout(timeoutMethod);
        timeoutEvent = sinon.spy();
        dialogue.on('timeout', timeoutEvent);
        dialogue.startTimeout();
        clock.tick(20);
        return timeoutEvent.should.have.calledOnce;
      • emits end event0ms

        var dialogue, end, timeoutMethod;
        dialogue = new Dialogue(testRes, {
          timeout: 10
        });
        end = sinon.spy();
        dialogue.on('end', end);
        timeoutMethod = sinon.spy();
        dialogue.onTimeout(timeoutMethod);
        dialogue.startTimeout();
        clock.tick(20);
        return end.should.have.calledOnce;
      • clears existing timeout1ms

        var dialogue;
        dialogue = new Dialogue(testRes, {
          timeout: 10
        });
        dialogue.onTimeout = sinon.spy();
        dialogue.startTimeout();
        dialogue.startTimeout();
        clock.tick(20);
        dialogue.clearTimeout.should.have.calledOnce;
        return dialogue.onTimeout.should.have.calledOnce;
      • calls onTimeout method0ms

        var dialogue;
        dialogue = new Dialogue(testRes, {
          timeout: 10
        });
        dialogue.onTimeout = sinon.spy();
        dialogue.startTimeout();
        clock.tick(20);
        return dialogue.onTimeout.should.have.calledOnce;
      • calls .end0ms

        var dialogue;
        dialogue = new Dialogue(testRes, {
          timeout: 10,
          timeoutText: null
        });
        dialogue.startTimeout();
        clock.tick(20);
        return dialogue.end.should.have.calledOnce;
    • .addPath

      • with a prompt, branches and key

        • returns new Path instance1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath('Turn left or right?', [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']], 'which-way'));
            return path.should.be["instanceof"](dialogue.Path);
          });
        • passes options to path1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath('Turn left or right?', [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']], 'which-way'));
            return path.key.should.eql('which-way');
          });
        • sends the prompt0ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath('Turn left or right?', [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']], 'which-way'));
            return dialogue.send.should.have.calledWith('Turn left or right?');
          });
        • starts timeout1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath('Turn left or right?', [[/left/, 'Ok, going left!'], [/right/, 'Ok, going right!']], 'which-way'));
            return dialogue.startTimeout.should.have.calledOnce;
          });
      • with branches only

        • returns new Path instance8ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath([[/1/, 'You get cake!'], [/2/, 'You get cake!']]));
            return path.should.be["instanceof"](dialogue.Path);
          });
        • sends nothing0ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath([[/1/, 'You get cake!'], [/2/, 'You get cake!']]));
            return dialogue.send.should.not.have.called;
          });
        • starts timeout1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath([[/1/, 'You get cake!'], [/2/, 'You get cake!']]));
            return dialogue.startTimeout.should.have.calledOnce;
          });
      • without branches

        • returns new Path instance1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath("Don't say nothing."));
            return path.should.be["instanceof"](dialogue.Path);
          });
        • does not start timeout1ms

          return co(function*() {
            var dialogue, path;
            dialogue = new Dialogue(testRes);
            path = (yield dialogue.addPath("Don't say nothing."));
            return dialogue.startTimeout.should.not.have.called;
          });
    • .addBranch

      • with existing path

        • passes branch args on to path.addBranch1ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.path = {
            addBranch: sinon.spy()
          };
          dialogue.addBranch(/foo/, 'foo');
          return dialogue.path.addBranch.should.have.calledWith(/foo/, 'foo');
        • starts timeout0ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.path = {
            addBranch: sinon.spy()
          };
          dialogue.addBranch(/foo/, 'foo');
          return dialogue.startTimeout.should.have.calledOnce;
      • when no path exists

        • creates a new path0ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.addBranch(/foo/, 'foo');
          return dialogue.path.should.be["instanceof"](dialogue.Path);
        • passes branch args on to path.addBranch1ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          sinon.spy(dialogue.Path.prototype, 'addBranch');
          dialogue.addBranch(/foo/, 'foo');
          return dialogue.path.addBranch.should.have.calledWith(/foo/, 'foo');
        • starts timeout0ms

          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.addBranch(/foo/, 'foo');
          return dialogue.startTimeout.should.have.calledOnce;
    • .receive

      • stores the latest response object3ms

        return co(function*() {
          var dialogue;
          dialogue = new Dialogue(testRes);
          dialogue.addBranch(/.*/, function() {});
          yield dialogue.receive(pretend.response('tester', 'new test'));
          return dialogue.res.message.text.should.equal('new test');
        });
      • attaches itself to the response10ms

        return co(function*() {
          var dialogue, newTestRes;
          dialogue = new Dialogue(testRes);
          dialogue.addBranch(/.*/, function() {});
          newTestRes = pretend.response('tester', 'new test');
          yield dialogue.receive(newTestRes);
          return newTestRes.dialogue.should.eql(dialogue);
        });
      • when already ended

        • returns false0ms

          return co(function*() {
            var dialogue, result;
            dialogue = new Dialogue(testRes);
            dialogue.end();
            result = (yield dialogue.receive(testRes));
            return result.should.be["false"];
          });
        • does not call the handler1ms

          return co(function*() {
            var callback, dialogue;
            dialogue = new Dialogue(testRes);
            callback = sinon.spy();
            dialogue.addBranch(/.*/, callback);
            dialogue.end();
            yield dialogue.receive(testRes);
            return callback.should.not.have.called;
          });
      • on matching branch

        • clears timeout3ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes, {
              timeout: 10
            });
            dialogue.addBranch(/foo/, function() {
              return null;
            });
            dialogue.onTimeout = sinon.spy();
            yield dialogue.receive(pretend.response('tester', 'foo'));
            clock.tick(20);
            dialogue.clearTimeout.should.have.calledOnce;
            return dialogue.onTimeout.should.not.have.called;
          });
        • ends dialogue3ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {
              return null;
            });
            yield dialogue.receive(pretend.response('tester', 'foo'));
            return dialogue.end.should.have.calledOnce;
          });
        • calls the branch handler4ms

          return co(function*() {
            var callback, dialogue;
            dialogue = new Dialogue(testRes);
            callback = sinon.spy();
            dialogue.addBranch(/foo/, 'bar', callback);
            yield dialogue.receive(pretend.response('tester', 'foo'));
            return callback.should.have.calledOnce;
          });
        • sends the branch message2ms

          return co(function*() {
            var callback, dialogue;
            dialogue = new Dialogue(testRes);
            callback = sinon.spy();
            dialogue.addBranch(/foo/, 'bar', callback);
            yield dialogue.receive(pretend.response('tester', 'foo'));
            return dialogue.send.should.have.calledWith('bar');
          });
      • on matching branches consecutively

        • only processes first match4ms

          return co(function*() {
            var callback, dialogue;
            dialogue = new Dialogue(testRes);
            callback = sinon.spy();
            dialogue.addBranch(/foo/, callback);
            dialogue.addBranch(/bar/, callback);
            yield dialogue.receive(pretend.response('tester', 'foo'));
            yield dialogue.receive(pretend.response('tester', 'bar'));
            return callback.should.have.calledOnce;
          });
      • on mismatch with catch

        • sends the catch message3ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {});
            dialogue.path.config.catchMessage = 'huh?';
            yield dialogue.receive(pretend.response('tester', '?'));
            return dialogue.send.should.have.calledWith('huh?');
          });
        • does not clear timeout10ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {});
            dialogue.path.config.catchMessage = 'huh?';
            yield dialogue.receive(pretend.response('tester', '?'));
            return dialogue.clearTimeout.should.not.have.called;
          });
        • does not call end3ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {});
            dialogue.path.config.catchMessage = 'huh?';
            yield dialogue.receive(pretend.response('tester', '?'));
            return dialogue.end.should.not.have.called;
          });
      • on mismatch without catch

        • does not clear timeout2ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {});
            yield dialogue.receive(pretend.response('tester', '?'));
            return dialogue.clearTimeout.should.not.have.called;
          });
        • does not call end2ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/foo/, function() {});
            yield dialogue.receive(pretend.response('tester', '?'));
            return dialogue.end.should.not.have.called;
          });
      • on matching branch that adds a new branch

        • added branches to current path2ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/more/, function() {
              dialogue.addBranch(/1/, 'got 1');
              return dialogue.addBranch(/2/, 'got 2');
            });
            yield dialogue.receive(pretend.response('tester', 'more'));
            return _.map(dialogue.path.branches, function(branch) {
              return branch.regex;
            }).should.eql([/more/, /1/, /2/]);
          });
        • does not call end2ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/more/, function() {
              dialogue.addBranch(/1/, 'got 1');
              return dialogue.addBranch(/2/, 'got 2');
            });
            yield dialogue.receive(pretend.response('tester', 'more'));
            return dialogue.end.should.not.have.called;
          });
      • on matching branch that adds a new path

        • added new branches to new path, overwrites prev path2ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/new/, function() {
              return dialogue.addPath([[/1/, 'got 1'], [/2/, 'got 2']]);
            });
            yield dialogue.receive(pretend.response('tester', 'new'));
            return _.map(dialogue.path.branches, function(branch) {
              return branch.regex;
            }).should.eql([/1/, /2/]);
          });
        • does not call end3ms

          return co(function*() {
            var dialogue;
            dialogue = new Dialogue(testRes);
            dialogue.addBranch(/new/, function() {
              return dialogue.addPath([[/1/, 'got 1'], [/2/, 'got 2']]);
            });
            yield dialogue.receive(pretend.response('tester', 'new'));
            return dialogue.end.should.not.have.called;
          });
  • Scene

    • constructor

      • without options

        • defaults to `user` scope1ms

          var scene;
          scene = new Scene(pretend.robot);
          return scene.config.scope.should.equal('user');
        • attaches receive middleware to robot0ms

          new Scene(pretend.robot);
          return pretend.robot.receiveMiddleware.should.have.calledOnce;
      • with options

        • stored options in config object0ms

          var scene;
          scene = new Scene(pretend.robot, {
            sendReplies: true
          });
          return scene.config.sendReplies.should.be["true"];
      • with room scope option

        • accepts given room scope0ms

          var scene;
          scene = new Scene(pretend.robot, {
            scope: 'room'
          });
          return scene.config.scope.should.equal('room');
        • stores config with default options for scope0ms

          var scene;
          scene = new Scene(pretend.robot, {
            scope: 'room'
          });
          return scene.config.sendReplies.should.be["true"];
      • with invalid scope

        • throws error when given invalid scope0ms

          try {
            new Scene(pretend.robot, {
              scope: 'monkey'
            });
          } catch (error1) {}
          return Scene.prototype.constructor.should["throw"];
    • .listen

      • accepts a string that can be cast as RegExp0ms

        var scene;
        scene = new Scene(pretend.robot);
        scene.listen('hear', '/test/i', function() {
          return null;
        });
        return pretend.robot.listeners.pop().regex.should.eql(/test/i);
      • with hear type and message matching regex

        • registers a robot hear listener with same id as scene1ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.listen('hear', /test/, function() {
            return null;
          });
          return pretend.robot.hear.should.have.calledWithMatch(sinon.match.regexp, sinon.match({
            id: scene.id
          }), sinon.match.func);
        • calls callback from listener when matched4ms

          return co(function*() {
            var callback, scene;
            scene = new Scene(pretend.robot);
            callback = sinon.spy();
            scene.listen('hear', /test/, callback);
            yield pretend.user('tester').send('test');
            return callback.should.have.calledOnce;
          });
        • callback should receive res and dialogue3ms

          var callback, scene;
          scene = new Scene(pretend.robot);
          callback = sinon.spy();
          scene.listen('hear', /test/, callback);
          return pretend.user('tester').send('test').then(function() {
            return callback.should.have.calledWith(matchRes);
          });
      • with respond type and message matching regex

        • registers a robot hear listener with same id as scene3ms

          var callback, id, scene;
          scene = new Scene(pretend.robot);
          callback = sinon.spy();
          id = scene.listen('respond', /test/, callback);
          return pretend.user('tester').send('hubot test').then(function() {
            return pretend.robot.respond.should.have.calledWithMatch(sinon.match.regexp, sinon.match({
              id: scene.id
            }), sinon.match.func);
          });
        • calls callback from listener when matched5ms

          return co(function*() {
            var callback, id, scene;
            scene = new Scene(pretend.robot);
            callback = sinon.spy();
            id = scene.listen('respond', /test/, callback);
            yield pretend.user('tester').send('hubot test');
            return callback.should.have.calledOnce;
          });
        • callback should receive res and dialogue3ms

          return co(function*() {
            var callback, id, scene;
            scene = new Scene(pretend.robot);
            callback = sinon.spy();
            id = scene.listen('respond', /test/, callback);
            yield pretend.user('tester').send('hubot test');
            return callback.should.have.calledWith(matchRes);
          });
      • with an invalid listener type

        • throws1ms

          var scene;
          scene = new Scene(pretend.robot);
          try {
            scene.listen('smell', /test/, function() {
              return null;
            });
          } catch (error1) {}
          return scene.listen.should["throw"];
      • with an invalid regex

        • throws0ms

          var scene;
          scene = new Scene(pretend.robot);
          try {
            scene.listen('hear', 'test', function() {
              return null;
            });
          } catch (error1) {}
          return scene.listen.should["throw"];
      • with an invalid callback

        • throws0ms

          var scene;
          scene = new Scene(pretend.robot);
          try {
            scene.listen('hear', /test/, {
              not: 'a function '
            });
          } catch (error1) {}
          return scene.listen.should["throw"];
    • .hear

      • calls .listen with hear listen type and arguments0ms

        var expectedArgs, ref, scene;
        scene = new Scene(pretend.robot);
        scene.hear(/test/, function() {
          return null;
        });
        expectedArgs = ['hear', /test/, sinon.match.func];
        return (ref = scene.listen.getCall(0).should.have).calledWith.apply(ref, expectedArgs);
    • .respond

      • calls .listen with respond listen type and arguments0ms

        var expectedArgs, ref, scene;
        scene = new Scene(pretend.robot);
        scene.respond(/test/, function() {
          return null;
        });
        expectedArgs = ['respond', /test/, sinon.match.func];
        return (ref = scene.listen.getCall(0).should.have).calledWith.apply(ref, expectedArgs);
    • .whoSpeaks

      • user scene

        • returns the ID of engaged user0ms

          var scene;
          scene = new Scene(pretend.robot, {
            scope: 'user'
          });
          return scene.whoSpeaks(pretend.lastReceive()).should.equal('user_111');
      • room sceene

        • returns the room ID0ms

          var scene;
          scene = new Scene(pretend.robot, {
            scope: 'room'
          });
          return scene.whoSpeaks(pretend.lastReceive()).should.equal('testing');
      • direct scene

        • returns the concatenated user ID and room ID0ms

          var scene;
          scene = new Scene(pretend.robot, {
            scope: 'direct'
          });
          return scene.whoSpeaks(pretend.lastReceive()).should.equal('user_111_testing');
    • .registerMiddleware

      • accepts function for enter middleware stack0ms

        var piece, scene;
        piece = function(context, next, done) {
          return null;
        };
        scene = new Scene(pretend.robot);
        scene.registerMiddleware(piece);
        return scene.enterMiddleware.stack[0].should.eql(piece);
      • throws when given function with incorrect arguments0ms

        var piece, scene;
        piece = function(notEnoughArgs) {
          return null;
        };
        scene = new Scene(pretend.robot);
        try {
          scene.registerMiddleware(piece);
        } catch (error1) {}
        scene.registerMiddleware.should["throw"];
        return scene.enterMiddleware.stack.length.should.equal(0);
    • .enter

      • returns a promise (then-able)0ms

        var promise, scene;
        scene = new Scene(pretend.robot);
        promise = scene.enter(pretend.lastReceive());
        return promise.then.should.be.a('function');
      • calls .processEnter with context1ms

        return co(function*() {
          var scene;
          scene = new Scene(pretend.robot);
          yield scene.enter(pretend.lastReceive());
          return scene.processEnter.should.have.calledOnce;
        });
      • resolves after callback called1ms

        return co(function*() {
          var callback, result, scene;
          scene = new Scene(pretend.robot);
          callback = sinon.spy();
          result = (yield scene.enter(pretend.lastReceive(), callback));
          return callback.should.have.calledOnce;
        });
      • exits (after event loop) if no dialogue paths added1ms

        return co(function*() {
          var scene;
          scene = new Scene(pretend.robot);
          yield scene.enter(pretend.lastReceive());
          yield setImmediatePromise();
          return scene.exit.should.have.calledWith(pretend.lastReceive(), 'no path');
        });
      • can use dialogue after yielding to prevent exit1ms

        return co(function*() {
          var context, scene;
          scene = new Scene(pretend.robot);
          context = (yield scene.enter(pretend.lastReceive()));
          context.dialogue.addBranch(matchAny, '');
          yield setImmediatePromise();
          return scene.exit.should.not.have.called;
        });
      • with callback (no middleware)

        • calls callback with final enter process context2ms

          var callback, keys, scene;
          scene = new Scene(pretend.robot);
          keys = ['response', 'participants', 'options', 'arguments', 'dialogue'];
          callback = function(result) {
            var ref;
            (ref = result.should.have.all).keys.apply(ref, keys);
            return done();
          };
          scene.enter(pretend.lastReceive(), callback);
      • with passing middleware

        • completes processing to resolve with context1ms

          return co(function*() {
            var keys, ref, result, scene;
            scene = new Scene(pretend.robot);
            keys = ['response', 'participants', 'options', 'arguments', 'dialogue'];
            scene.registerMiddleware(function(context, next, done) {
              return next();
            });
            result = (yield scene.enter(pretend.lastReceive()));
            return (ref = result.should.have.all).keys.apply(ref, keys);
          });
        • calls callback with final enter process context0ms

          var callback, keys, scene;
          scene = new Scene(pretend.robot);
          keys = ['response', 'participants', 'options', 'arguments', 'dialogue'];
          callback = function(result) {
            var ref;
            (ref = result.should.have.all).keys.apply(ref, keys);
            return done();
          };
          scene.registerMiddleware(function(context, next, done) {
            return next();
          });
          scene.enter(pretend.lastReceive(), callback);
      • with blocking middleware

        • rejects promise1ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.registerMiddleware(function(context, next, done) {
            return done();
          });
          return scene.enter(pretend.lastReceive()).then(function() {
            throw new Error('promise should have caught');
          })["catch"](function(err) {
            err.should.be["instanceof"](Error);
            return err.should.have.property('message', 'Middleware piece called done');
          });
        • does not complete or call .processEnter1ms

          return co(function*() {
            var error, scene;
            scene = new Scene(pretend.robot);
            scene.registerMiddleware(function(context, next, done) {
              return done();
            });
            try {
              yield scene.enter(pretend.lastReceive());
            } catch (error1) {
              error = error1;
            }
            return scene.processEnter.should.not.have.called;
          });
      • with custom done function override

        • completes processing and calls custom done1ms

          return co(function*() {
            var custom, scene;
            scene = new Scene(pretend.robot);
            custom = sinon.spy();
            scene.registerMiddleware(function(context, next, done) {
              return next(function() {
                custom();
                return done();
              });
            });
            yield scene.enter(pretend.lastReceive());
            return custom.should.have.calledOnce;
          });
      • with multiple middleware pieces

        • calls each sequentially passing ammended context1ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.registerMiddleware(function(context, next, done) {
            context.trace = ['A'];
            return next();
          });
          scene.registerMiddleware(function(context, next, done) {
            context.trace.push('B');
            return next();
          });
          scene.registerMiddleware(function(context, next, done) {
            context.trace.push('C');
            return next();
          });
          scene.enter(pretend.lastReceive(), function(result) {
            result.trace.should.eql(['A', 'B', 'C']);
            return done();
          });
        • does not process enter if any middleware blocks0ms

          return co(function*() {
            var error, scene;
            scene = new Scene(pretend.robot);
            scene.registerMiddleware(function(context, next, done) {
              return next();
            });
            scene.registerMiddleware(function(context, next, done) {
              return done();
            });
            scene.registerMiddleware(function(context, next, done) {
              return next();
            });
            try {
              yield scene.enter(pretend.lastReceive());
            } catch (error1) {
              error = error1;
            }
            return scene.processEnter.should.not.have.called;
          });
      • user scene

        • saves engaged Dialogue instance with user ID1ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'user'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.engaged['user_111'].should.be["instanceof"](Dialogue);
          });
      • room scene

        • saves engaged Dialogue instance with room key0ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'room'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.engaged['testing'].should.be["instanceof"](Dialogue);
          });
      • direct scene

        • saves engaged Dialogue instance with composite key1ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'direct'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.engaged['user_111_testing'].should.be["instanceof"](Dialogue);
          });
      • with timeout options

        • passes the options to dialogue config0ms

          function* () {
                    var dialogue, scene;
                    scene = new Scene(pretend.robot);
                    dialogue = (yield scene.enter(pretend.lastReceive(), {
                      timeout: 100,
                      timeoutText: 'foo'
                    })).dialogue;
                    dialogue.config.timeout.should.equal(100);
                    return dialogue.config.timeoutText.should.equal('foo');
                  }
      • dialogue allowed to timeout after branch added

        • calls .exit first on "timeout"12ms

          var res, scene;
          scene = new Scene(pretend.robot);
          res = pretend.lastReceive();
          scene.enter(res, {
            timeout: 10,
            timeoutText: null
          }).then(function(context) {
            context.dialogue.on('end', function() {
              scene.exit.should.have.calledWith(res, 'timeout');
              return done();
            });
            context.dialogue.addBranch(matchAny, '');
            return wait(20);
          });
        • calls .exit again on "incomplete"11ms

          var res, scene;
          scene = new Scene(pretend.robot);
          res = pretend.lastReceive();
          scene.enter(res, {
            timeout: 10,
            timeoutText: null
          }).then(function(context) {
            context.dialogue.on('end', function() {
              scene.exit.should.have.calledWith(res, 'incomplete');
              return done();
            });
            context.dialogue.addBranch(matchAny, '');
            return wait(20);
          });
      • dialogue completed (by message matching branch)

        • calls .exit once only2ms

          return co(function*() {
            var context, scene;
            scene = new Scene(pretend.robot);
            context = (yield scene.enter(pretend.lastReceive()));
            context.dialogue.addBranch(matchAny, '');
            yield pretend.user('tester').send('test');
            yield pretend.user('tester').send('testing again');
            return scene.exit.should.have.calledOnce;
          });
        • calls .exit once with last (matched) res and "complete"1ms

          return co(function*() {
            var context, scene;
            scene = new Scene(pretend.robot);
            context = (yield scene.enter(pretend.lastReceive()));
            context.dialogue.addBranch(matchAny, '');
            yield pretend.user('tester').send('test');
            yield pretend.user('tester').send('testing again');
            return scene.exit.should.have.calledWith(context.dialogue.res, 'complete');
          });
      • re-enter currently engaged participants

        • returns error the second time1ms

          return co(function*() {
            var resultA, resultB, scene;
            scene = new Scene(pretend.robot);
            resultA = (yield scene.enter(pretend.lastReceive()));
            try {
              resultB = (yield scene.enter(pretend.lastReceive()));
            } catch (error1) {}
            return scene.enter.should["throw"];
          });
        • rejected enter can be caught0ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.enter(pretend.lastReceive()).then(function() {
            return scene.enter(pretend.lastReceive())["catch"](function(err) {
              err.should.be["instanceof"](Error);
              return done();
            });
          });
      • re-enter previously engaged participants

        • returns Dialogue instance (as per normal)2ms

          return co(function*() {
            var dialogue, scene;
            scene = new Scene(pretend.robot);
            yield scene.enter(pretend.lastReceive());
            scene.exit(pretend.lastReceive());
            dialogue = (yield scene.enter(pretend.lastReceive())).dialogue;
            return dialogue.should.be["instanceof"](Dialogue);
          });
    • .exit

      • with user in scene, called manually

        • does not call onTimeout on dialogue25ms

          return co(function*() {
            var dialogue, scene, timeout;
            scene = new Scene(pretend.robot);
            dialogue = (yield scene.enter(pretend.lastReceive(), {
              timeout: 10
            })).dialogue;
            dialogue.addBranch(matchAny, '');
            timeout = sinon.spy();
            dialogue.onTimeout(timeout);
            scene.exit(pretend.lastReceive(), 'testing exits');
            yield wait(20);
            return timeout.should.not.have.called;
          });
        • removes the dialogue instance from engaged array1ms

          return co(function*() {
            var dialogue, scene;
            scene = new Scene(pretend.robot);
            dialogue = (yield scene.enter(pretend.lastReceive(), {
              timeout: 10
            })).dialogue;
            dialogue.addBranch(matchAny, '');
            scene.exit(pretend.lastReceive(), 'testing exits');
            return should.not.exist(scene.engaged['user_111']);
          });
        • returns true22ms

          return co(function*() {
            var dialogue, scene;
            scene = new Scene(pretend.robot);
            dialogue = (yield scene.enter(pretend.lastReceive(), {
              timeout: 10
            })).dialogue;
            dialogue.addBranch(matchAny, '');
            scene.exit(pretend.lastReceive(), 'testing exits');
            yield wait(20);
            return scene.exit.returnValues.pop().should.be["true"];
          });
        • logged the reason22ms

          return co(function*() {
            var dialogue, scene;
            scene = new Scene(pretend.robot);
            scene.id = 'scene_111';
            dialogue = (yield scene.enter(pretend.lastReceive(), {
              timeout: 10
            })).dialogue;
            dialogue.addBranch(matchAny, '');
            scene.exit(pretend.lastReceive(), 'testing exits');
            yield wait(20);
            return pretend.logs.pop().should.eql(['info', 'Disengaged user user_111 (testing exits) (id: scene_111)']);
          });
        • dialogue does not continue receiving after scene exit20ms

          return co(function*() {
            var dialogue, scene;
            scene = new Scene(pretend.robot);
            dialogue = (yield scene.enter(pretend.lastReceive(), {
              timeout: 10
            })).dialogue;
            dialogue.addBranch(matchAny, '');
            dialogue.receive = sinon.spy();
            scene.exit(pretend.lastReceive(), 'tester');
            pretend.user('tester').send('test');
            yield wait(20);
            return dialogue.receive.should.not.have.called;
          });
      • with user in scene, called from events

        • gets called twice (on timeout and end)14ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.enter(pretend.lastReceive(), {
            timeout: 10
          }).then(function(context) {
            context.dialogue.on('end', function() {
              scene.exit.should.have.calledTwice;
              return done();
            });
            context.dialogue.addBranch(matchAny, '');
            return wait(20);
          });
        • returns true the first time13ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.enter(pretend.lastReceive(), {
            timeout: 10
          }).then(function(context) {
            context.dialogue.on('end', function() {
              scene.exit.getCall(0).should.have.returned(true);
              return done();
            });
            context.dialogue.addBranch(matchAny, '');
            return wait(20);
          });
        • returns false the second time (because already disengaged)12ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.enter(pretend.lastReceive(), {
            timeout: 10
          }).then(function(context) {
            context.dialogue.on('end', function() {
              scene.exit.getCall(1).should.have.returned(false);
              return done();
            });
            context.dialogue.addBranch(matchAny, '');
            return wait(20);
          });
      • user not in scene, called manually

        • returns false1ms

          var scene;
          scene = new Scene(pretend.robot);
          scene.exit(pretend.lastReceive(), 'testing exits');
          return scene.exit.returnValues.pop().should.be["false"];
    • .exitAll

      • with two users in scene

        • created two dialogues4ms

          return co(function*() {
            var A, B, scene;
            scene = new Scene(pretend.robot);
            A = (yield scene.enter(pretend.response('A', 'test')));
            B = (yield scene.enter(pretend.response('B', 'test')));
            scene.exitAll();
            A.dialogue.should.be["instanceof"](Dialogue);
            return B.dialogue.should.be["instanceof"](Dialogue);
          });
        • calls clearTimeout on both dialogues5ms

          return co(function*() {
            var A, B, resA, resB, scene;
            scene = new Scene(pretend.robot);
            resA = pretend.response('A', 'test');
            resB = pretend.response('B', 'test');
            A = (yield scene.enter(resA));
            A.dialogue.addBranch(matchAny, '');
            B = (yield scene.enter(resB));
            B.dialogue.addBranch(matchAny, '');
            A.dialogue.clearTimeout = sinon.spy();
            B.dialogue.clearTimeout = sinon.spy();
            scene.exitAll();
            A.dialogue.clearTimeout.should.have.calledOnce;
            return B.dialogue.clearTimeout.should.have.calledOnce;
          });
        • has no remaining engaged dialogues4ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot);
            yield scene.enter(pretend.response('A', 'test'));
            yield scene.enter(pretend.response('B', 'test'));
            scene.exitAll();
            return scene.engaged.length.should.equal(0);
          });
    • .getDialogue

      • with user in scene

        • returns the matching dialogue0ms

          return co(function*() {
            var dialogue, result, scene;
            scene = new Scene(pretend.robot);
            dialogue = (yield scene.enter(pretend.lastReceive())).dialogue;
            result = scene.getDialogue('user_111');
            return dialogue.should.eql(result);
          });
      • no user in scene

        • returns undefined0ms

          var dialogue, scene;
          scene = new Scene(pretend.robot);
          dialogue = scene.getDialogue('user_111');
          return should.not.exist(dialogue);
    • .inDialogue

      • in engaged user scene

        • returns true with user ID0ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot);
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('user_111').should.be["true"];
          });
        • returns false with room name1ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot);
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('testing').should.be["false"];
          });
      • participant not in scene

        • returns false0ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot);
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('user_222').should.be["false"];
          });
      • room scene, in scene

        • returns true with roomname1ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'room'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('testing').should.be["true"];
          });
        • returns false with user ID0ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'room'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('user_111').should.be["false"];
          });
      • direct scene, in scene

        • returns true with userID_roomID2ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'direct'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('user_111_testing').should.be["true"];
          });
        • returns false with roomname1ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'direct'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('testing').should.be["false"];
          });
        • returns false with user ID2ms

          return co(function*() {
            var scene;
            scene = new Scene(pretend.robot, {
              scope: 'direct'
            });
            yield scene.enter(pretend.lastReceive());
            return scene.inDialogue('user_111').should.be["false"];
          });
  • Director

    • constructor

      • without optional args

        • has empty array names0ms

          return this.director.names.should.eql([]);
      • with authorise function

        • stores the given function as its authorise method0ms

          return this.director.authorise = this.authorise;
      • with options (denied reply and key string)

        • stores passed options in config0ms

          return this.director.config.deniedReply.should.equal("DENIED!");
      • with env var for config

        • has default config with env inherited0ms

          return this.director.config.should.eql({
            type: 'whitelist',
            scope: 'username',
            deniedReply: "403 Sorry."
          });
      • with env var for names

        • whitelist type, username scope

          • stores the whitelisted usernames from env0ms

            return this.director.names.should.eql(['Emmanuel']);
        • whitelist type, room scope

          • stores the whitelisted rooms from env0ms

            return this.director.names.should.eql(['Capital']);
        • blacklist type, username scope

          • stores the blacklisted usernames from env0ms

            return this.director.names.should.eql(['Winston', 'Julia', 'Syme']);
        • blacklist type, room scope

          • stores the blacklisted rooms from env0ms

            return this.director.names.should.eql(['Labour']);
      • with invalid option for type

        • should throw error0ms

          return Director.prototype.constructor.should["throw"];
      • with invalid option for scope

        • should throw error0ms

          return Director.prototype.constructor.should["throw"];
      • without key, with authorise function and options

        • uses options0ms

          return this.director.config.scope.should.equal('room');
        • uses authorise function0ms

          return this.director.authorise.should.eql(this.authorise);
    • .add

      • given array of names

        • stores them in the names array0ms

          return this.director.names.should.eql(['pema', 'nima']);
      • given single name

        • stores it in the names array0ms

          return this.director.names.should.eql(['pema']);
      • given array of names, some existing

        • adds any missing, not duplicating existing0ms

          return this.director.names.should.eql(['yeon', 'juan', 'pema']);
    • .remove

      • given array of names

        • removes them from the names array0ms

          return this.director.names.should.eql(['yeon', 'juan']);
      • with single name

        • removes it from the names array0ms

          return this.director.names.should.eql(['yeon', 'juan', 'nima']);
      • with array names, some not existing

        • removes any missing, ignoring others0ms

          return this.director.names.should.eql(['yeon']);
    • .isAllowed

      • whitelist without authorise function

        • no list

          • returns false1ms

            var director;
            director = new Director(pretend.robot);
            return director.isAllowed(pretend.response('tester', 'test')).should.be["false"];
        • has list, username on list

          • returns true2ms

            var director;
            director = new Director(pretend.robot);
            director.names = ['tester'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["true"];
        • has list, username not on list

          • returns false2ms

            var director;
            director = new Director(pretend.robot);
            director.names = ['nobody'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["false"];
      • blacklist without authorise function

        • no list

          • returns true1ms

            var director;
            director = new Director(pretend.robot, {
              type: 'blacklist'
            });
            return director.isAllowed(pretend.response('tester', 'test')).should.be["true"];
        • has list, username on list

          • returns false2ms

            var director;
            director = new Director(pretend.robot, {
              type: 'blacklist'
            });
            director.names = ['tester'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["false"];
        • has list, username not on list

          • returns true2ms

            var director;
            director = new Director(pretend.robot, {
              type: 'blacklist'
            });
            director.names = ['nobody'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["true"];
      • whitelist with authorise function

        • no list

          • calls authorise function with username and res1ms

            var authorise, director, res;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise);
            res = pretend.response('tester', 'test');
            director.isAllowed(res);
            return authorise.should.have.calledWith('tester', res);
          • returns value of authorise function1ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise);
            return director.isAllowed(pretend.response('tester', 'test')).should.equal('AUTHORISE');
        • has list, username on list

          • returns true2ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise);
            director.names = ['tester'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["true"];
          • does not call authorise function1ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise);
            director.names = ['tester'];
            director.isAllowed(pretend.response('tester', 'test'));
            return authorise.should.not.have.been.calledOnce;
        • has list, username not on list

          • returns value of authorise function2ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise);
            director.names = ['nobody'];
            return director.isAllowed(pretend.response('tester', 'test')).should.equal('AUTHORISE');
      • blacklist with authorise function

        • no list

          • calls authorise function with username and res2ms

            var authorise, director, res;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise, {
              type: 'blacklist'
            });
            res = pretend.response('tester', 'test');
            director.isAllowed(res);
            return authorise.should.have.calledWith('tester', res);
          • returns value of authorise function2ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise, {
              type: 'blacklist'
            });
            return director.isAllowed(pretend.response('tester', 'test')).should.equal('AUTHORISE');
        • has list, username on list

          • returns false2ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise, {
              type: 'blacklist'
            });
            director.names = ['tester'];
            return director.isAllowed(pretend.response('tester', 'test')).should.be["false"];
          • does not call authorise function2ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise, {
              type: 'blacklist'
            });
            director.names = ['tester'];
            director.isAllowed(pretend.response('tester', 'test'));
            return authorise.should.not.have.been.calledOnce;
        • has list, username not on list

          • returns value of authorise function1ms

            var authorise, director;
            authorise = sinon.spy(function() {
              return 'AUTHORISE';
            });
            director = new Director(pretend.robot, authorise, {
              type: 'blacklist'
            });
            director.names = ['nobody'];
            return director.isAllowed(pretend.response('tester', 'test')).should.equal('AUTHORISE');
      • room scope, blacklist room

        • returns false2ms

          var director;
          director = new Director(pretend.robot, {
            type: 'blacklist',
            scope: 'room'
          });
          director.names = ['testing'];
          return director.isAllowed(pretend.response('tester', 'test', 'testing')).should.be["false"];
      • room scope, whitelist room

        • returns true3ms

          var director;
          director = new Director(pretend.robot, {
            type: 'whitelist',
            scope: 'room'
          });
          director.names = ['testing'];
          return director.isAllowed(pretend.response('tester', 'test', 'testing')).should.be["true"];
    • .process

      • calls .isAllowed to determine if user is allowed or denied2ms

        var director, res, scene;
        director = new Director(pretend.robot);
        scene = new Scene(pretend.robot);
        res = pretend.response('tester', 'testing');
        director.process(res);
        return director.isAllowed.should.have.calledWith(res);
      • returns a promise2ms

        var director, scene;
        director = new Director(pretend.robot);
        scene = new Scene(pretend.robot);
        return director.process(pretend.response('tester', 'testing')).then.should.be.a('function');
      • resolves to .isAllowed result3ms

        return co(function*() {
          var director, result, scene;
          director = new Director(pretend.robot);
          scene = new Scene(pretend.robot);
          result = (yield director.process(pretend.response('tester', 'testing')));
          return result.should.equal(director.isAllowed.returnValues.pop());
        });
      • with async auth function

        • resolves with auth function result after finished34ms

          return co(function*() {
            var authorise, director, result;
            authorise = function() {
              return new Promise(function(resolve, reject) {
                var done;
                done = function() {
                  return resolve('AUTHORISE');
                };
                return setTimeout(done, 30);
              });
            };
            director = new Director(pretend.robot, authorise);
            result = (yield director.process(pretend.response('tester', 'test')));
            return result.should.equal('AUTHORISE');
          });
      • denied with denied reply value

        • calls response method reply with reply value3ms

          return co(function*() {
            var director, res;
            director = new Director(pretend.robot, {
              deniedReply: 'DENIED'
            });
            res = pretend.response('tester', 'test');
            yield director.process(res);
            return res.reply.should.have.calledWith('DENIED');
          });
      • denied without denied reply value

        • does not call response reply method2ms

          return co(function*() {
            var director, res;
            director = new Director(pretend.robot);
            res = pretend.response('tester', 'test');
            yield director.process(res);
            return res.reply.should.not.have.called;
          });
      • allowed user with denied reply value

        • calls .isAllowed to determine if user is allowed or denied2ms

          return co(function*() {
            var director, res;
            director = new Director(pretend.robot);
            director.names = ['tester'];
            res = pretend.response('tester', 'test');
            yield director.process(res);
            return director.isAllowed.should.have.calledWith(res);
          });
        • resolves to same value as .isAllowed2ms

          return co(function*() {
            var director, result;
            director = new Director(pretend.robot);
            director.names = ['tester'];
            result = (yield director.process(pretend.response('tester', 'test')));
            return result.should.equal(director.isAllowed.returnValues.pop());
          });
        • does not send denied reply2ms

          return co(function*() {
            var director, res;
            director = new Director(pretend.robot);
            director.names = ['tester'];
            res = pretend.response('tester', 'test');
            yield director.process(res);
            return res.reply.should.not.have.called;
          });
    • .directMatch

      • allowed user sending message matching directed match

        • calls .process to perform access checks and reply2ms

          return co(function*() {
            var director;
            director = new Director(pretend.robot);
            pretend.robot.hear(/let me in/, function() {});
            director.directMatch(/let me in/);
            director.names = ['tester'];
            yield pretend.user('tester').send('let me in');
            return director.process.should.have.calledOnce;
          });
        • triggers match callback normally2ms

          return co(function*() {
            var callback, director;
            director = new Director(pretend.robot);
            callback = sinon.spy();
            pretend.robot.hear(/let me in/, callback);
            director.directMatch(/let me in/);
            director.names = ['tester'];
            yield pretend.user('tester').send('let me in');
            return callback.should.have.calledOnce;
          });
      • denied user sending message matching directed match

        • calls .process to perform access checks and reply7ms

          return co(function*() {
            var director;
            director = new Director(pretend.robot);
            pretend.robot.hear(/let me in/, function() {});
            director.directMatch(/let me in/);
            yield pretend.user('tester').send('let me in');
            return director.process.should.have.calledOnce;
          });
        • prevents match callback from triggering1ms

          return co(function*() {
            var callback, director;
            director = new Director(pretend.robot);
            callback = sinon.spy();
            pretend.robot.hear(/let me in/, callback);
            director.directMatch(/let me in/);
            yield pretend.user('tester').send('let me in');
            return callback.should.not.have.called;
          });
      • denied user sending unmatched message

        • does not call .process because middleware did not match1ms

          return co(function*() {
            var director;
            director = new Director(pretend.robot);
            pretend.robot.hear(/let me in/, function() {});
            director.directMatch(/let me in/);
            yield pretend.user('tester').send('foo');
            return director.process.should.not.have.called;
          });
    • .directListener

      • with message matching directed listener id

        • calls .process to perform access checks and reply1ms

          return co(function*() {
            var director;
            director = new Director(pretend.robot);
            pretend.robot.hear(/let me in/, {
              id: 'entry-test'
            }, function() {});
            director.directListener('entry-test');
            yield pretend.user('tester').send('let me in');
            return director.process.should.have.calledOnce;
          });
        • triggers match callback when allowed2ms

          return co(function*() {
            var callback, director;
            director = new Director(pretend.robot);
            callback = sinon.spy();
            pretend.robot.hear(/let me in/, {
              id: 'entry-test'
            }, callback);
            director.directListener('entry-test');
            director.names = ['tester'];
            yield pretend.user('tester').send('let me in');
            return callback.should.have.calledOnce;
          });
        • prevents match callback when denied2ms

          return co(function*() {
            var callback, director;
            director = new Director(pretend.robot);
            callback = sinon.spy();
            pretend.robot.hear(/let me in/, {
              id: 'entry-test'
            }, callback);
            director.directListener('entry-test');
            yield pretend.user('tester').send('let me in');
            return callback.should.not.have.called;
          });
      • with unmatched message

        • does not call .process because middleware did not match1ms

          return co(function*() {
            var director;
            director = new Director(pretend.robot);
            pretend.robot.hear(/let me in/, {
              id: 'entry-test'
            }, function() {});
            director.directListener('entry-test');
            yield pretend.user('tester').send('foo');
            return director.process.should.not.have.called;
          });
    • .directScene

      • scene enter middleware calls director .process3ms

        var director, res, scene;
        director = new Director(pretend.robot);
        scene = new Scene(pretend.robot);
        director.directScene(scene);
        res = pretend.response('tester', 'test');
        return scene.enter(res)["catch"](function() {
          return director.process.should.have.calledWith(res);
        });
      • user allowed

        • allowed scene enter, resolves with context3ms

          var director, keys, scene;
          director = new Director(pretend.robot);
          scene = new Scene(pretend.robot);
          keys = ['response', 'participants', 'options', 'arguments', 'dialogue'];
          director.directScene(scene);
          director.names = ['tester'];
          return scene.enter(pretend.response('tester', 'test')).then(function(result) {
            var ref;
            return (ref = result.should.have.all).keys.apply(ref, keys);
          });
      • user denied

        • preempts scene enter, rejects promise3ms

          var director, scene;
          director = new Director(pretend.robot);
          scene = new Scene(pretend.robot);
          director.directScene(scene);
          return scene.enter(pretend.response('tester', 'test')).then(function() {
            throw new Error('promise should have caught');
          })["catch"](function(err) {
            return err.should.be["instanceof"](Error);
          });
      • with multiple scenes, only one directed

        • calls process only once for the directed scene4ms

          return co(function*() {
            var director, resA, resB, sceneA, sceneB;
            director = new Director(pretend.robot);
            sceneA = new Scene(pretend.robot);
            sceneB = new Scene(pretend.robot);
            director.directScene(sceneA);
            resA = pretend.response('tester', 'let me in A');
            resB = pretend.response('tester', 'let me in A');
            try {
              yield sceneA.enter(resA);
              yield sceneB.enter(resB);
            } catch (error) {}
            director.process.should.have.calledOnce;
            return director.process.should.have.calledWithExactly(resA);
          });
      • allowed user sends message matching scene listener

        • allows scene to process entry36ms

          var callback, director, scene;
          director = new Director(pretend.robot);
          scene = new Scene(pretend.robot);
          director.directScene(scene);
          director.names = ['tester'];
          callback = sinon.spy();
          scene.hear(/let me in/, callback);
          pretend.user('tester').send('let me in');
          return setTimeout(function() {
            scene.processEnter.should.have.calledOnce;
            callback.should.have.calledOnce;
            return done();
          }, 35);
      • denied user sends message matching scene listener

        • prevents the scene from processing entry37ms

          var callback, director, scene;
          director = new Director(pretend.robot);
          scene = new Scene(pretend.robot);
          director.directScene(scene);
          callback = sinon.spy();
          scene.hear(/let me in/, callback);
          pretend.user('tester').send('let me in');
          return setTimeout(function() {
            scene.processEnter.should.not.have.called;
            callback.should.not.have.called;
            return done();
          }, 35);
  • Transcript

    • constructor

      • with saving enabled (default)

        • uses brain for record keeping0ms

          return this.transcript.records.should.eql([
            {
              time: 0,
              event: 'test'
            }
          ]);
      • with saving disabled

        • keeps records in a new empty array1ms

          return this.transcript.records.should.eql([]);
    • .recordEvent

      • emitted from Hubot/brain

        • records event "other" data0ms

          return this.transcript.records.should.eql([
            {
              time: 0,
              event: 'mockEvent',
              other: [
                {
                  test: 'data'
                }
              ]
            }
          ]);
      • emitted from Playbook module

        • with default config

          • records default instance attributes3ms

            this.transcript.on('record', (function(_this) {
              return function() {
                _this.transcript.records[0].should.containSubset({
                  instance: {
                    name: _this.module.name,
                    key: _this.module.key,
                    id: _this.module.id
                  }
                });
                return done();
              };
            })(this));
            return this.module.emit('mockEvent', pretend.response('tester', 'test'));
          • records default response attributes1ms

            var res;
            res = pretend.response('tester', 'test');
            this.transcript.on('record', (function(_this) {
              return function() {
                _this.transcript.records[0].should.containSubset({
                  response: {
                    match: res.match
                  }
                });
                return done();
              };
            })(this));
            return this.module.emit('mockEvent', res);
          • records default message attributes3ms

            var res;
            res = pretend.response('tester', 'test', 'testing');
            this.transcript.on('record', (function(_this) {
              return function() {
                _this.transcript.records[0].should.containSubset({
                  message: {
                    user: {
                      id: res.message.user.id,
                      name: res.message.user.name
                    },
                    room: res.message.room,
                    text: res.message.text
                  }
                });
                return done();
              };
            })(this));
            return this.module.emit('mockEvent', pretend.response('tester', 'test'));
        • with transcript key

          • records event with key property0ms

            return this.transcript.records[0].should.have.property('key', 'test-key');
        • with custom instance atts

          • records custom instance attributes1ms

            return this.transcript.records[0].should.containSubset({
              instance: {
                name: this.module.name,
                config: {
                  scope: this.module.config.scope
                }
              }
            });
        • with custom response atts

          • records custom response attributes0ms

            return this.transcript.records[0].should.containSubset({
              response: {
                message: {
                  room: 'testing'
                }
              }
            });
        • with custom message atts

          • records custom message attributes0ms

            return this.transcript.records[0].should.containSubset({
              message: {
                room: 'testing'
              }
            });
        • on event without res argument

          • records event without response or other attributes0ms

            return this.transcript.records.should.eql([
              {
                time: 0,
                event: 'mockEvent',
                instance: {
                  name: this.module.name,
                  key: this.module.key,
                  id: this.module.id
                }
              }
            ]);
        • with invalid custom response atts

          • records event without response attributes1ms

            return this.transcript.records.should.eql([
              {
                time: 0,
                event: 'mockEvent',
                instance: {
                  name: this.module.name,
                  key: this.module.key,
                  id: this.module.id
                }
              }
            ]);
          • does not throw0ms

            return this.transcript.recordEvent.should.not["throw"];
    • .recordAll

      • with default event set

        • records default events only1ms

          return this.transcript.recordEvent.args.should.eql([['match'], ['mismatch'], ['catch'], ['send']]);
      • with custom event set

        • records custom events only0ms

          return this.transcript.recordEvent.args.should.eql([['foo'], ['bar']]);
    • .recordDialogue

      • with default event set

        • attached listener for default events from dialogue and path1ms

          var expectedEvents;
          expectedEvents = this.transcript.config.events;
          expectedEvents.push('path');
          return _.keys(pretend.robot._events).should.have.members(expectedEvents);
        • calls the listener when event emited from dialogue path0ms

          return this.transcript.recordEvent.should.have.calledWith('match', this.dialogue.path);
      • with custom event set

        • attached listener for default events from dialogue and path0ms

          return _.keys(pretend.robot._events).should.have.members(['match', 'mismatch', 'end', 'path']);
        • calls the listener when event emited from dialogue0ms

          return this.transcript.recordEvent.should.have.calledWith('end', this.dialogue);
        • calls the listener when event emited from path0ms

          return this.transcript.recordEvent.should.have.calledWith('match', this.dialogue.path);
        • does not call with any unconfigured events0ms

          return this.transcript.recordEvent.should.not.have.calledWith('send', this.dialogue);
    • .recordScene

      • records events emitted by scene, its dialogues and paths5ms

        return co(function*() {
          var dialogue, records, res, scene, transcript;
          res = pretend.response('tester', 'test');
          removeListeners(pretend.robot);
          transcript = new Transcript(pretend.robot, {
            save: false,
            events: ['enter', 'match', 'send']
          });
          scene = new Scene(pretend.robot);
          transcript.recordScene(scene);
          dialogue = (yield scene.enter(res)).dialogue;
          dialogue.addBranch(/test/, 'response');
          yield dialogue.receive(res);
          records = transcript.recordEvent.args.map(function(record) {
            return _.take(record, 2);
          });
          return records.should.eql([['enter', scene], ['match', dialogue.path], ['send', dialogue]]);
        });
    • .recordDirector

      • attached listeners for director events1ms

        var director, transcript;
        removeListeners(pretend.robot);
        transcript = new Transcript(pretend.robot, {
          save: false
        });
        director = new Director(pretend.robot, {
          type: 'blacklist'
        });
        transcript.recordDirector(director);
        return _.keys(pretend.robot._events).should.eql(['allow', 'deny']);
      • records events emitted by director2ms

        return co(function*() {
          var director, res, transcript;
          removeListeners(pretend.robot);
          transcript = new Transcript(pretend.robot, {
            save: false
          });
          director = new Director(pretend.robot, {
            type: 'blacklist'
          });
          res = pretend.response('tester', 'test');
          transcript.recordDirector(director);
          director.names = ['tester'];
          yield director.process(res);
          director.config.type = 'whitelist';
          yield director.process(res);
          return transcript.recordEvent.args.should.eql([['deny', director, res], ['allow', director, res]]);
        });
    • .findRecords

      • with record subset matcher

        • returns records matching given attributes1ms

          return this.transcript.findRecords({
            message: {
              user: {
                name: 'jon'
              }
            }
          }).should.eql([
            {
              time: 0,
              event: 'match',
              instance: {
                key: 'time'
              },
              message: {
                user: {
                  name: 'jon'
                },
                text: 'now'
              }
            }, {
              time: 0,
              event: 'match',
              instance: {
                key: 'direction'
              },
              message: {
                user: {
                  name: 'jon'
                },
                text: 'left'
              }
            }
          ]);
      • with record subset matcher

        • returns only the values at given path1ms

          return this.transcript.findRecords({
            message: {
              user: {
                name: 'jon'
              }
            }
          }, 'message.text').should.eql(['now', 'left']);
    • .findKeyMatches

      • with an instance key and capture group

        • returns the answers matching the key0ms

          return this.transcript.findKeyMatches('pick-a-color', 0).should.eql(['blue', 'orange', 'red']);
      • with an instance key, user ID and capture group

        • returns the answers matching the key for the user0ms

          return this.transcript.findKeyMatches('pick-a-color', '111', 0).should.eql(['blue', 'orange']);
  • Outline

    • constructor

      • without options

        • calls setupScenes0ms

          let outline = new Outline(pretend.robot, [
            { key: 'foo', condition: /foo/i, send: 'foo' },
            { key: 'bar', condition: /bar/i, send: 'bar', listen: 'hear' }
          ])
          outline.setupScenes.should.have.calledWith()
      • with setupScenes option disabled

        • does not call setupScenes0ms

          let outline = new Outline(pretend.robot, [
            { key: 'foo', condition: /foo/i, send: 'foo' },
            { key: 'bar', condition: /bar/i, send: 'bar', listen: 'hear' }
          ], { setupScenes: false })
          outline.setupScenes.should.not.have.calledWith()
      • with bit missing a key

        • throws an error0ms

          let bits = [{ condition: /test/i, send: 'testing' }]
          let outline, err
          try {
            outline = new Outline(pretend.robot, bits)
          } catch (e) {
            err = e
          }
          err.should.be.instanceof(Error)
          err.message.should.match(/key/)
          should.not.exist(outline)
    • .getByKey

      • returns bit with given key0ms

        let outline = new Outline(pretend.robot, [
          { key: 'foo', condition: /foo/i, send: 'foo' },
          { key: 'bar', condition: /bar/i, send: 'bar' }
        ], { setupScenes: false })
        outline.getByKey('foo')
        .should.eql({ key: 'foo', condition: /foo/i, send: 'foo' })
      • throws if key is not found / invalid0ms

        let outline = new Outline(pretend.robot, [
          { key: 'foo', condition: /foo/i, send: 'foo' },
          { key: 'bar', condition: /bar/i, send: 'bar' }
        ], { setupScenes: false })
        let result, err
        try {
          result = outline.getByKey('baz')
        } catch (e) {
          err = e
        }
        err.should.be.instanceof(Error)
        err.message.should.match(/invalid/)
        should.not.exist(result)
    • .parseCondition

      • with regex condition

        • returns exact regex for bit's condition1ms

          let bits = [{ key: 'testing', condition: /test/i }]
          let outline = new Outline(pretend.robot, bits, { setupScenes: false })
          let testString = 'Test'
          let testMatch = testString.match(/test/i)
          _.isRegExp(outline.parseCondition(bits[0].condition)).should.equal(true)
          testString.match(outline.parseCondition(bits[0].condition)).should.eql(testMatch)
          // console.log(bits[0].condition, testString.match(outline.parseCondition(bits[0].condition)))
      • with string contraining regex

        • creates a valid regex from content of string0ms

          let bits = [{ key: 'testing', condition: '/test/i' }]
          let outline = new Outline(pretend.robot, bits, { setupScenes: false })
          let testString = 'Test'
          let testMatch = testString.match(/test/i)
          _.isRegExp(outline.parseCondition(bits[0].condition)).should.equal(true)
          testString.match(outline.parseCondition(bits[0].condition)).should.eql(testMatch)
          // console.log(bits[0].condition, testString.match(outline.parseCondition(bits[0].condition)))
      • with simple word only string

        • creates a valid (case-insensative) regex from word0ms

          let bits = [{ key: 'testing', condition: 'test' }]
          let outline = new Outline(pretend.robot, bits, { setupScenes: false })
          let testString = 'Test'
          let testMatch = testString.match(/test/i)
          _.isRegExp(outline.parseCondition(bits[0].condition)).should.equal(true)
          testString.match(outline.parseCondition(bits[0].condition)).should.eql(testMatch)
          // console.log(bits[0].condition, testString.match(outline.parseCondition(bits[0].condition)))
    • setupScenes

      • without Playbook

        • throws1ms

          delete pretend.robot.playbook
          let outline = new Outline(pretend.robot, [
            { key: 'foo', condition: /foo/i, send: 'foo' },
            { key: 'bar', condition: /bar/i, send: 'bar', listen: 'hear' }
          ], { setupScenes: false })
          try { outline.setupScenes() } catch (e) {}
          outline.setupScenes.should.have.thrown('Error')
      • with Playbook

        • calls sceneListen on Playbook with bit args1ms

          sinon.spy(playbook, 'sceneListen')
          let outline = new Outline(pretend.robot, [
            { key: 'foo', condition: /foo/i, send: 'foo' },
            {
              key: 'bar',
              condition: /bar/i,
              send: 'bar',
              listen: 'hear',
              options: { timeout: 100, scope: 'direct' }
            },
            { key: 'baz', condition: /baz/i, send: 'baz' }
          ], { setupScenes: false })
          outline.setupScenes()
          playbook.sceneListen.should.have.calledOnce // eslint-disable-line
          playbook.sceneListen.should.have.calledWithExactly(
            'hear', /bar/i, { timeout: 100, scope: 'direct' }, 'bar', sinon.match.func
          )
        • returns the outline for chaining0ms

          let outline = new Outline(pretend.robot, [
            { key: 'foo', condition: /foo/i, send: 'foo' },
            { key: 'bar', condition: /bar/i, send: 'bar' }
          ], { setupScenes: false })
          outline.setupScenes()
          .should.eql(outline)
    • .setupDialogue

      • adds bit options to dialogue config3ms

        let res = pretend.response('tester', 'test')
        let outline = new Outline(pretend.robot)
        let dialogue = new Dialogue(res)
        res.dialogue = dialogue
        res.bit = {
          key: 'foo',
          condition: /foo/i,
          send: 'foo',
          listen: 'hear',
          options: { timeout: 100, scope: 'direct' }
        }
        outline.setupDialogue(res)
        dialogue.config.should.containSubset({
          timeout: 100, scope: 'direct'
        })
    • .bitCallback

      • returns promise2ms

        let res = pretend.response('tester', 'test')
        let outline = new Outline(pretend.robot)
        res.dialogue = new Dialogue(res)
        let bit = { key: 'foo', condition: /foo/i, send: 'foo' }
        outline.bitCallback(bit, res)
        .then.should.be.a('function')
      • adds bit to res1ms

        let res = pretend.response('tester', 'test')
        let outline = new Outline(pretend.robot)
        res.dialogue = new Dialogue(res)
        let bit = { key: 'foo', condition: /foo/i, send: 'foo' }
        outline.bitCallback(bit, res)
        res.bit.should.eql(bit)
      • sets up dialogue2ms

        async function () {
              let res = pretend.response('tester', 'test')
              let outline = new Outline(pretend.robot)
              res.dialogue = new Dialogue(res)
              let bit = { key: 'foo', condition: /foo/i, send: 'foo' }
              await outline.bitCallback(bit, res)
              outline.setupDialogue.should.have.calledWith(res)
            }
      • sends bit send strings2ms

        async function () {
              let res = pretend.response('tester', 'test')
              let outline = new Outline(pretend.robot)
              res.dialogue = new Dialogue(res)
              let bit = { key: 'foo', condition: /foo/i, send: ['foo', 'bar'] }
              await outline.bitCallback(bit, res)
              pretend.messages.should.eql([
                [ 'hubot', 'foo' ],
                [ 'hubot', 'bar' ]
              ])
            }
      • with bits to do next

        • sets up paths3ms

          async function () {
                  let res = pretend.response('tester', 'test')
                  let bits = [
                    { key: 'foo', condition: /foo/i, send: 'foo', next: ['bar'] },
                    { key: 'bar', condition: /bar/i, send: 'bar' }
                  ]
                  let outline = new Outline(pretend.robot, bits)
                  res.dialogue = new Dialogue(res)
                  await outline.bitCallback(bits[0], res)
                  outline.setupPath.should.have.calledWith(res)
                }
      • with nothing to do next

        • resolves without setting up path2ms

          async function () {
                  let res = pretend.response('tester', 'test')
                  let bits = [{ key: 'foo', condition: /foo/i, send: 'foo' }]
                  let outline = new Outline(pretend.robot, bits)
                  res.dialogue = new Dialogue(res)
                  outline.bitCallback(bits[0], res)
                  outline.setupPath.should.not.have.calledOnce // eslint-disable-line
                }
    • .setupPath

      • creates path and branches for next bits2ms

        let res = pretend.response('tester', 'test')
        let bits = [
          { key: 'foo', condition: /foo/i, send: 'foo', next: ['bar', 'baz'] },
          { key: 'bar', condition: /bar/i, send: 'bar' },
          { key: 'baz', condition: /baz/i, send: 'baz' }
        ]
        let outline = new Outline(pretend.robot, bits)
        res.dialogue = new Dialogue(res)
        res.dialogue.addPath = sinon.spy()
        res.bit = bits[0]
        outline.setupPath(res)
        res.dialogue.addPath.should.have.calledWith([
          [/bar/i, sinon.match.func],
          [/baz/i, sinon.match.func]
        ]) // eslint-disable-line
      • added bit catch property as path option2ms

        async function () {
              let res = pretend.response('tester', 'test')
              let bits = [
                { key: 'foo', condition: /foo/i, send: 'foo', next: ['bar'], catch: 'foo?' },
                { key: 'bar', condition: /bar/i, send: 'bar' }
              ]
              let outline = new Outline(pretend.robot, bits)
              res.dialogue = new Dialogue(res)
              res.bit = bits[0]
              await outline.setupPath(res)
              res.dialogue.path.config.catchMessage.should.equal(bits[0].catch)
            }
      • executes cyclical interaction from connected bits5ms

        async function () {
              pretend.robot.playbook.outline([
                { key: 'foo', condition: /foo/i, send: 'foo!', next: ['bar', 'baz'], listen: 'hear' },
                { key: 'bar', condition: /bar/i, send: 'bar!', next: ['foo', 'baz'] },
                { key: 'baz', condition: /bar/i, send: 'bar!', next: ['foo', 'bar'] }
              ])
              await pretend.user('tester').send('foo?')
              await pretend.user('tester').send('bar?')
              await pretend.user('tester').send('baz?')
              await pretend.user('tester').send('foo?')
              await pretend.user('tester').send('baz?')
              await pretend.user('tester').send('bar?')
              pretend.messages.should.eql([
                [ 'tester', 'foo?' ],
                [ 'hubot', 'foo!' ],
                [ 'tester', 'bar?' ],
                [ 'hubot', 'bar!' ],
                [ 'tester', 'baz?' ],
                [ 'tester', 'foo?' ],
                [ 'hubot', 'foo!' ],
                [ 'tester', 'baz?' ],
                [ 'tester', 'bar?' ],
                [ 'hubot', 'bar!' ]
              ])
            }
  • Improv

    • singleton

      • sequential use returns existing instance0ms

        let improvA = improv.use(pretend.robot)
        let improvB = improv.use(pretend.robot)
        improvA.should.eql(improvB)
      • instance persists after test robot shutdown0ms

        should.exist(improv.instance)
      • use after clear returns new instance0ms

        let improvA = improv.use(pretend.robot)
        improv.reset()
        let improvB = improv.use(pretend.robot)
        improvA.should.not.eql(improvB)
      • overwrite robot when reused0ms

        improv.use(pretend.robot).robot.should.eql(pretend.robot)
      • configuration merges existing config1ms

        improv.configure({ foo: 'bar' })
        improv.configure({ baz: 'qux' })
        improv.instance.config.should.include({ foo: 'bar', baz: 'qux' })
    • instance

      • .use

        • attaches response middleware to robot0ms

          pretend.robot.responseMiddleware.should.have.callCount(1)
      • .extend

        • with function only

          • stores function in extensions array with undefined path1ms

            let func = sinon.spy()
            improv.extend(func)
            improv.extensions.should.eql([{ 'function': func, path: void 0 }])
        • with function and path

          • stores function in extensions array with undefined path0ms

            let func = sinon.spy()
            improv.extend(func, 'a.path')
            improv.extensions.should.eql([{ 'function': func, path: 'a.path' }])
      • .remember

        • stores data at key in data0ms

          improv.data.site = { name: 'Hub' }
          improv.remember('site', { lang: 'en' })
          improv.data.should.eql({ site: { lang: 'en' } })
        • stores data at path in data0ms

          improv.data.site = { name: 'Hub' }
          improv.remember('site.lang', 'en')
          improv.data.should.eql({ site: { name: 'Hub', lang: 'en' } })
      • .rememberForUser

        • stores data at path with user ID0ms

          let uid = pretend.lastListen().message.user.id
          improv.data.site = { name: 'Hub' }
          improv.rememberForUser(uid, 'site.nickname', 'Hubby')
          improv.userData.should.eql({ [uid]: { site: { nickname: 'Hubby' } } })
      • .forget

        • removes data at path0ms

          improv.data.site = { name: 'Hub', lang: 'en' }
          improv.remember('site.lang', 'en')
          improv.data.should.eql({ site: { name: 'Hub', lang: 'en' } })
      • .forgetForUser

        • forgets user data at path for user ID1ms

          let uid = pretend.lastListen().message.user.id
          improv.userData = { [uid]: { site: { nickname: 'Hubby' } } }
          improv.forgetForUser(uid, 'site.nickname')
          improv.userData.should.eql({ [uid]: { site: {} } })
      • .mergeData

        • with data passed as option

          • merges data with given context0ms

            improv.data.site = { name: 'Hub' }
            improv.mergeData({ user: pretend.lastListen().message.user })
            .should.eql({
              user: pretend.lastListen().message.user,
              site: { name: 'Hub' }
            })
          • makes user from res in context available1ms

            improv.data.site = { name: 'Hub' }
            improv.mergeData({ response: pretend.lastListen() })
            .should.eql({
              response: pretend.lastListen(),
              user: pretend.lastListen().message.user,
              site: { name: 'Hub' }
            })
          • merges data with remembered user data in context0ms

            let uid = pretend.lastListen().message.user.id
            improv.data.site = { name: 'Hub' }
            improv.userData = { [uid]: { site: { nickname: 'Hubby' } } }
            improv.mergeData({ response: pretend.lastListen() })
              .should.eql({
                response: pretend.lastListen(),
                user: pretend.lastListen().message.user,
                site: { name: 'Hub', nickname: 'Hubby' }
              })
          • does not merge remembered user data without context0ms

            let uid = pretend.lastListen().message.user.id
            improv.data.site = { name: 'Hub' }
            improv.userData = { [uid]: { site: { nickname: 'Hubby' } } }
            improv.mergeData().should.eql({ site: { name: 'Hub' } })
        • with data loaded from brain

          • merges data with user data0ms

            improv.configure({ save: true })
            pretend.robot.brain.set('improv', { site: { owner: 'Hubot' } })
            improv.data.site = { name: 'Hub' }
            improv.mergeData({ user: pretend.lastListen().message.user })
            .should.eql({
              user: pretend.lastListen().message.user,
              site: { owner: 'Hubot', name: 'Hub' }
            })
        • with extension functions added

          • merges data with results of functions0ms

            improv
              .extend(() => { return { custom1: 'foo' } })
              .extend(() => { return { custom2: 'bar' } })
              .mergeData({ user: pretend.lastListen().message.user })
              .should.eql({
                user: pretend.lastListen().message.user,
                custom1: 'foo',
                custom2: 'bar'
              })
          • deep merges existing data with extensions1ms

            improv
              .extend(() => { return { user: { type: 'human' } } })
              .mergeData({ user: { name: 'frendo' } })
              .should.eql({ user: { name: 'frendo', type: 'human' } })
        • with paths argument matching extension

          • merges extension with existing data0ms

            let func = () => { return { test: { foo: 'bar' } } }
            improv
              .extend(func, 'test.foo')
              .mergeData({ test: { baz: 'qux' } }, ['test.foo'])
              .should.eql({ test: { foo: 'bar', baz: 'qux' } })
        • with paths partially matching extension

          • merges extension with existing data0ms

            let func = () => { return { test: { foo: 'bar' } } }
            improv
              .extend(func, 'test.foo')
              .mergeData({ test: { baz: 'qux' } }, ['test'])
              .should.eql({ test: { foo: 'bar', baz: 'qux' } })
        • with paths that don't match extension

          • returns extension data only0ms

            let func = () => { return { test: { foo: 'bar' } } }
            improv
              .extend(func, 'test.foo')
              .mergeData({ test: { baz: 'qux' } }, ['something.else'])
              .should.eql({ test: { baz: 'qux' } })
      • .parse

        • with data

          • populates message template with data at path0ms

            improv.data = { site: 'The Hub' }
            improv
              .parse({ strings: ['welcome to ${ this.site }'] })
              .should.eql(['welcome to The Hub'])
        • without data

          • uses fallback value1ms

            let string = 'hey ${this.user.name}, pay ${this.product.price}'
            improv
              .parse({ strings: [string] })
              .should.eql(['hey unknown, pay unknown'])
        • with partial data

          • uses fallback for unknowns0ms

            let string = 'hey ${ this.user.name }, pay ${ this.product.price }'
            improv.data = { product: { price: '$55' } }
            improv
              .parse({ strings: [string] })
              .should.eql(['hey unknown, pay $55'])
          • replaces entire string as configured1ms

            let string = 'hey ${this.user.name}, pay ${this.product.price}'
            improv.configure({ replacement: '¯\\_(ツ)_/¯' })
            improv.data = { product: { price: '$55' } }
            improv
              .parse({ strings: [string] })
              .should.eql(['¯\\_(ツ)_/¯'])
      • .middleware

        • with series of hubot sends

          • rendered messages with data0ms

            async () => {
                      improv.data.site = { name: 'The Hub' }
                      await pretend.lastListen().send('hello you')
                      await pretend.lastListen().send('hi ${ this.user.name }')
                      pretend.messages.should.eql([
                        ['testing', 'tester', 'test'],
                        ['testing', 'hubot', 'hello you'],
                        ['testing', 'hubot', 'hi tester']
                      ])
                    }
        • with multiple strings

          • renders each message with data0ms

            async () => {
                      improv.data.site = { name: 'The Hub' }
                      await pretend.lastListen().send(
                        'testing',
                        'hi ${ this.user.name }',
                        'welcome to ${ this.site.name }'
                      )
                      pretend.messages.should.eql([
                        ['testing', 'tester', 'test'],
                        ['testing', 'hubot', 'testing'],
                        ['testing', 'hubot', 'hi tester'],
                        ['testing', 'hubot', 'welcome to The Hub']
                      ])
                    }
          • renders messages with remembered user data2ms

            async () => {
                      pretend.robot.hear(/remember i like (.*)/, (res) => {
                        improv.rememberForUser(res.message.user.id, 'fav', res.match[1])
                      })
                      await pretend.user('blueboi').send('remember i like blue')
                      await pretend.lastListen().send('i know you like ${this.fav}')
                      pretend.messages.should.eql([
                        ['testing', 'tester', 'test'],
                        ['blueboi', 'remember i like blue'],
                        ['hubot', 'i know you like blue']
                      ])
                    }
          • does not confuse user memory in succession3ms

            async () => {
                      pretend.robot.hear(/remember i like (.*)/, (res) => {
                        improv.rememberForUser(res.message.user.id, 'fav', res.match[1])
                      })
                      await pretend.user('blueboi').send('remember i like blue')
                      let blueres = pretend.lastListen()
                      await pretend.user('redkid').send('remember i like red')
                      let redres = pretend.lastListen()
                      await blueres.reply('i know you like ${this.fav}')
                      await redres.reply('i know you like ${this.fav}')
                      pretend.messages.should.eql([
                        ['testing', 'tester', 'test'],
                        ['blueboi', 'remember i like blue'],
                        ['redkid', 'remember i like red'],
                        ['hubot', '@blueboi i know you like blue'],
                        ['hubot', '@redkid i know you like red']
                      ])
                    }
  • Playbook - singleton

    • require returns instance0ms

      return playbook.constructor.name.should.equal('Playbook');
    • instance contains modules0ms

      return playbook.should.containSubset({
        Dialogue: require('../../lib/modules/dialogue'),
        Scene: require('../../lib/modules/scene'),
        Director: require('../../lib/modules/director'),
        Transcript: require('../../lib/modules/transcript'),
        Outline: require('../../lib/modules/outline'),
        improv: require('../../lib/modules/improv')
      });
    • re-require returns the same instance0ms

      playbook.foo = 'bar';
      playbook = require('../../lib');
      return playbook.foo.should.equal('bar');
    • .reset

      • returns new instance0ms

        playbook.foo = 'bar';
        playbook = playbook.reset();
        return should.not.exist(playbook.foo);
    • .use

      • attaches robot5ms

        pretend.start();
        return playbook.use(pretend.robot).should.have.property('robot', pretend.robot);
      • inherits robot log5ms

        pretend.start();
        return playbook.use(pretend.robot).should.have.property('log', pretend.log);
  • Playbook

    • dialogue

      • creates Dialogue instance2ms

        return playbook.dialogue(pretend.response('tester', 'test')).should.be["instanceof"](playbook.Dialogue);
      • stores it in the dialogues array1ms

        var dialogue;
        dialogue = playbook.dialogue(pretend.response('tester', 'test'));
        return playbook.dialogues[0].should.eql(dialogue);
    • scene

      • makes a Scene :P1ms

        return playbook.scene().should.be["instanceof"](playbook.Scene);
      • stores it in the scenes array0ms

        var scene;
        scene = playbook.scene();
        return playbook.scenes[0].should.eql(scene);
    • .sceneEnter

      • without type or args (other than response)

        • makes scene with default user type10ms

          return playbook.sceneEnter(pretend.response('tester', 'test', 'testing')).then(function() {
            return playbook.scenes[0].should.be["instanceof"](playbook.Scene);
          });
        • resolves with a context object2ms

          var keys;
          keys = ['response', 'participants', 'options', 'arguments', 'dialogue'];
          return playbook.sceneEnter(pretend.response('tester', 'test')).then(function(result) {
            var ref;
            return (ref = result.should.have.all).keys.apply(ref, keys);
          });
        • enters scene, engaging user (stores against id)2ms

          return playbook.sceneEnter(pretend.response('tester', 'test')).then(function(context) {
            return playbook.scenes[0].engaged[pretend.users.tester.id].should.eql(context.dialogue);
          });
      • with type and options args

        • used the given room type2ms

          var res;
          res = pretend.response('tester', 'test', 'testing');
          return playbook.sceneEnter(res, {
            scope: 'room',
            sendReplies: false
          }).then(function() {
            return playbook.scenes[0].config.scope.should.equal('room');
          });
        • passed the scene options to dialogue1ms

          var res;
          res = pretend.response('tester', 'test', 'testing');
          return playbook.sceneEnter(res, {
            scope: 'room',
            sendReplies: false
          }).then(function(context) {
            return context.dialogue.config.sendReplies.should.be["false"];
          });
    • .sceneListen

      • with scene args

        • creates Scene instance0ms

          return this.scene.should.be["instanceof"](playbook.Scene);
        • passed args to the scene0ms

          return playbook.scene.should.have.calledWith({
            sendReplies: false,
            scope: 'room'
          });
        • calls .listen on the scene with type, regex and callback0ms

          return this.listen.should.have.calledWith('hear', /test/, sinon.match.func);
      • without scene args

        • creates Scene instance0ms

          return this.scene.should.be["instanceof"](playbook.Scene);
        • passed no args to the scene0ms

          return playbook.scene.getCall(0).should.have.calledWith();
        • calls .listen on the scene with type, regex and callback0ms

          return this.listen.should.have.calledWith('hear', /test/, sinon.match.func);
    • .sceneHear

      • calls .sceneListen with hear type and any other args0ms

        var args, ref;
        args = [
          'hear', /test/, {
            scope: 'room'
          }, sinon.match.func
        ];
        return (ref = playbook.sceneListen.lastCall.should.have).calledWith.apply(ref, args);
    • .sceneRespond

      • calls .sceneListen with respond type and any other args0ms

        var args, ref;
        args = [
          'respond', /test/, {
            scope: 'room'
          }, sinon.match.func
        ];
        return (ref = playbook.sceneListen.getCall(0).should.have).calledWith.apply(ref, args);
    • .director

      • creates and returns director0ms

        return this.director.should.be["instanceof"](playbook.Director);
      • stores it in the directors array0ms

        return playbook.directors[0].should.eql(this.director);
    • .transcript

      • creates and returns transcript0ms

        return this.transcript.should.be["instanceof"](playbook.Transcript);
      • stores it in the transcripts array0ms

        return playbook.transcripts[0].should.eql(this.transcript);
    • .transcribe

      • creates transcripts for each module0ms

        return playbook.transcript.should.have.calledThrice;
      • records events from given instances in brain1ms

        return pretend.robot.brain.get('transcripts').should.eql([
          {
            time: 0,
            event: 'deny',
            instance: {
              name: 'director'
            }
          }, {
            time: 0,
            event: 'enter',
            instance: {
              name: 'scene'
            }
          }, {
            time: 0,
            event: 'send',
            instance: {
              name: 'dialogue'
            },
            strings: ['test']
          }
        ]);
    • .improvise

      • returns the improv singleton0ms

        return playbook.improvise().should.eql(playbook.improv);
      • with non-improv playbook

        • does not parse messages2ms

          return co(function*() {
            var res;
            res = pretend.response('tester', 'test');
            yield res.send('hello ${this.user.name}');
            return pretend.messages.pop().should.eql(['hubot', 'hello ${this.user.name}']);
          });
        • parses after called4ms

          return co(function*() {
            var res;
            res = pretend.response('tester', 'test');
            playbook.improvise();
            yield res.send('hello ${ this.user.name }');
            return pretend.messages.pop().should.eql(['hubot', 'hello tester']);
          });
      • using custom data transforms

        • parses messages with extended context2ms

          return co(function*() {
            var res;
            res = pretend.response('tester', 'test');
            playbook.improv.extend(function(data) {
              data.user.name = data.user.name.toUpperCase();
              return data;
            });
            yield res.send('hello ${ this.user.name }');
            return pretend.messages.pop().should.eql(['hubot', 'hello TESTER']);
          });
      • extended using transcript reocrds

        • merge the recorded answers with attribute tags3ms

          return co(function*() {
            var dialogue, transcript;
            dialogue = (yield playbook.sceneEnter(pretend.response('tester', 'test'))).dialogue;
            transcript = playbook.transcribe(dialogue, {
              events: ['match']
            });
            playbook.improv.extend(function(data) {
              var userColors, userId;
              userId = data.user.id;
              userColors = transcript.findKeyMatches('fav-color', data.user.id, 0);
              if (userColors.length) {
                return {
                  user: {
                    favColor: userColors.pop()
                  }
                };
              }
            });
            yield dialogue.addPath('what is your favourite colour?', [[/.*/, 'nice! mine is ${ this.user.favColor } too!']], 'fav-color');
            yield pretend.user('tester').send('orange');
            return pretend.messages.should.eql([['hubot', 'what is your favourite colour?'], ['tester', 'orange'], ['hubot', 'nice! mine is orange too!']]);
          });
    • .shutdown

      • calls .exitAll on scenes1ms

        var exit, scene;
        scene = playbook.scene();
        exit = sinon.spy(scene, 'exitAll');
        playbook.shutdown();
        return exit.should.have.calledOnce;
      • calls .end on dialogues1ms

        var dialogue, end;
        dialogue = playbook.dialogue(pretend.response('tester', 'test'));
        end = sinon.spy(dialogue, 'end');
        playbook.shutdown();
        return end.should.have.calledOnce;
    • .reset

      • shuts down original instance1ms

        sinon.spy(playbook, 'shutdown');
        playbook.reset();
        return playbook.shutdown.should.have.calledOnce;
      • clears existing instance collections0ms

        playbook.dialogues = [
          {
            name: 'foo'
          }, {
            name: 'bar'
          }
        ];
        playbook.scenes = [
          {
            name: 'foo'
          }, {
            name: 'bar'
          }
        ];
        playbook.directors = [
          {
            name: 'foo'
          }, {
            name: 'bar'
          }
        ];
        playbook.transcripts = [
          {
            name: 'foo'
          }, {
            name: 'bar'
          }
        ];
        playbook.outlines = [
          {
            name: 'foo'
          }, {
            name: 'bar'
          }
        ];
        playbook = playbook.reset();
        playbook.dialogues.should.eql([]);
        playbook.scenes.should.eql([]);
        playbook.transcripts.should.eql([]);
        return playbook.outlines.should.eql([]);
      • returns re-initialised instance0ms

        playbook = playbook.reset();
        return should.not.exist(playbook.robot);