Commit 8c05ca54 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Class to detect @username style replies in comments.

Included a mini-library 'regexp-quote' which provides just one but
useful function.
parent 5f459002
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
var regexpQuote = require("regexp-quote");

/**
 * @singleton
 * Detection of `@username` style replies inside comments.
 */
var ReplyDetector = {
    /**
     * Determines if the comment is intended as a reply to some of the
     * other users who have posted in this thread.
     *
     * @param {String} comment Plain comment text that was posted.
     * @param {Object[]} users Array of users who have posted to this
     * thread.  This should be a unique list without duplicates and
     * without the user who's the author of posted comment.
     * @return {Object[]} Array of users that were mentioned in the
     * comment, if any.
     */
    detect: function(comment, users) {
        // sort users with longer names to be at the beginning.
        // This way when comment mentions @john.doe
        // and we have users "john" and "john.doe",
        // then user "john.doe" will be matched first.
        users.sort(function(a, b) {
            return b.username.length - a.username.length;
        });

        // split comment into parts where each part begins with @-sign
        var parts = comment.split(/@/);
        // ignore the part before the first @-sign
        parts.shift();

        // check each remaining part against all the usernames
        var detectedUsers = [];
        parts.forEach(function(snippet) {
            var user = this.matchUser(snippet, users);
            if (user && detectedUsers.indexOf(user) === -1) {
                detectedUsers.push(user);
            }
        }.bind(this));

        return detectedUsers;
    },

    // Returns the first user in list who's name matches with the
    // beginning of the given text snippet.
    matchUser: function(snippet, users) {
        for (var i=0; i<users.length; i++) {
            var user = users[i];
            var re = new RegExp('^'+regexpQuote(user.username) + '\\b', "i");
            if (re.test(snippet)) {
                return user;
            }
        }
        return false;
    }
};

module.exports = ReplyDetector;
+2 −1
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
        "connect-mysql-session": "~0.1",
        "marked": "~0.2",
        "sanitizer": "~0.0",
        "nodemailer": "~0.3"
        "nodemailer": "~0.3",
        "regexp-quote": "0.0.0"
    }
}
+97 −0
Original line number Diff line number Diff line
describe("ReplyDetector#detect", function() {
    var ReplyDetector = require("../lib/reply_detector");

    // helpers
    function objWrap(username) {
        return {username: username};
    }
    function objUnwrap(user) {
        return user.username;
    }

    function detect(comment, users) {
        return ReplyDetector.detect(comment, users.map(objWrap)).map(objUnwrap);
    }

    // no replies

    it("finds no replies if no users given", function() {
        expect(detect("@rene: Blah", [])).toEqual([]);
    });

    it("finds no replies if text contains no '@' char at all", function() {
        expect(detect("Some text... blah blah...", ["mary", "john"])).toEqual([]);
    });

    it("finds no replies if @mentions don't match up with users", function() {
        expect(detect("@fred: blah blah...", ["mary", "john"])).toEqual([]);
    });

    it("finds no replies if @mentions don't exactly match up with users", function() {
        expect(detect("@johnny: blah blah...", ["mary", "john"])).toEqual([]);
    });

    // single reply

    it("finds a reply to john when text begins with @john", function() {
        expect(detect("@john Thanks.", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to john when text begins with @john:", function() {
        expect(detect("@john: Thanks.", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to john when text begins with **@john:**", function() {
        expect(detect("**@john:** Thanks.", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to john when text begins with @John", function() {
        expect(detect("@John Thanks.", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to john when text contains @john", function() {
        expect(detect("Hello @john, Thanks.", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to john when text ends with @john", function() {
        expect(detect("Thanks @john", ["mary", "john", "nige"])).toEqual(["john"]);
    });

    it("finds a reply to 15john8 when text contains @15john8", function() {
        expect(detect("Hello @15john8, Thanks.", ["john", "15john8", "15"])).toEqual(["15john8"]);
    });

    it("finds a reply to foo*bar when text contains @foo*bar", function() {
        expect(detect("Hello @foo*bar, Thanks.", ["nick", "foo*bar", "john"])).toEqual(["foo*bar"]);
    });

    it("finds a reply to susan.dada when text contains @susan.dada", function() {
        expect(detect("Hello @susan.dada, Thanks.", ["susan", "susan.dada", "dada"])).toEqual(["susan.dada"]);
    });

    it("finds a reply to John Lennon when text contains @John Lennon:", function() {
        expect(detect("To @John Lennon: Don't know", ["Susan", "John Lennon"])).toEqual(["John Lennon"]);
    });

    it("Finds a reply to John when text contains @John and we have users 'John' and 'John Lennon' ", function() {
        expect(detect("@John: WTF!", ["John Lennon", "John"])).toEqual(["John"]);
    });

    // multiple replies

    it("finds a reply to both john & mary when lines in text begin with @john and @mary", function() {
        expect(detect("@john Agree.\n@mary Thanks.", ["john", "nige", "mary"])).toEqual(["john", "mary"]);
    });

    it("finds a reply to both john & mary when text contains @john and @mary", function() {
        expect(detect("**@john, @mary:** I agree!", ["john", "nige", "mary"])).toEqual(["john", "mary"]);
    });

    // duplicate replies

    it("finds a single reply to john when @john mentioned multiple times", function() {
        expect(detect("@john:Thanks.\n@john: and be good.", ["ronald", "john", "mary"])).toEqual(["john"]);
    });


});