<commit_before>/**
 * Parses text to return to the log items
 * Created by cos-caner on 12/4/15.
 * Remember to embed nodes, user, linked_node and template_node in api call i.e. var url = $osf.apiV2Url('nodes/' + nodeId + '/logs/', { query : { 'embed' : ['nodes', 'user']}});
 */
var m = require('mithril'); // exposes mithril methods, useful for redraw etc.
var logActions = require('json!js/_allLogTexts.json');
var $ = require('jquery');  // jQuery
var $osf = require('js/osfHelpers');

var ravenMessagesCache = []; // Cache messages to avoid sending multiple times in one page view
/**
 * Utility function to not repeat logging errors to Sentry
 * @param message {String} Custom message for error
 * @param logObject {Object} the entire log object returned from the api
 */
function ravenMessage (message, logObject) {
    if(ravenMessagesCache.indexOf(message) === -1){
        Raven.captureMessage(message, {logObject: logObject});
        ravenMessagesCache.push(message);
    }
}

/**
 * Checks if the required parameter to complete the log is returned
 * This may intentionally not be returned to make log anonymous
 * @param param {string|number} The parameter to be used
 * @param logObject {Object} the entire log object returned from the api
 * @returns {boolean}
 */
var paramIsReturned = function _paramIsReturned (param, logObject){
    if(!param){
        var message = 'Expected parameter for Log action ' + logObject.attributes.action + ' was not returned from log api.';
        ravenMessage(message, logObject);
        return false;
    }
    if (param.errors){
        return false;
    }
    return true;
};

/**
 * Returns the text parameters since their formatting is mainly the same
 * @param param {string} The parameter to be used, has to be available under logObject.attributes.param
 * @param text {string} The text to be used if param is not available
 * @param logObject {Object} the entire log object returned from the api
 * @returns {*}
 */
var returnTextParams = function (param, text, logObject) {
    var source = logObject.attributes.params[param];

    if(paramIsReturned(source, logObject)){
        if($.isArray(source)){
            return m('span', [
                source.map(function(item, index, arr){
                    if(arr.length === 1 && index === 0){
                        return m('span', item);
                    }
                    if(index === arr.length-1) {
                        return m('span', ', and ' + item);
                    }
                    return m('span', item + ', ');
                })
            ]);
        }
        return m('span', '"' + source + '"');
    }
    return m('span', text);
};

var LogText = {
    view : function(ctrl, logObject) {
        var text = logActions[logObject.attributes.action];
        var message = '';
        if(text){
            var list = text.split(/(\${.*?})/);
            return m('span.osf-log-item',[
                list.map(function(piece){
                    if (piece === '') {
                        return m('span');
                    }
                    var startsWith = piece.substring(0,2);
                    if(startsWith === '${'){
                        var last = piece.length-1;
                        var logComponentName = piece.substring(2,last);
                        if(LogPieces[logComponentName]){
                            return m.component(LogPieces[logComponentName], logObject);
                        } else {
                            message = 'There is no template in logTextParser.js for  ' + logComponentName + '.';
                            ravenMessage(message, logObject);
                            return m('');
                        }
                    }
                    return piece;
                })
            ]);
        } else {
            message = 'There is no text entry in dictionary for the action :' + logObject.attributes.action;
            ravenMessage(message, logObject);
            return m('');
        }
    }
};

var LogPieces = {
    // User that took the action
    user: {
        view: function (ctrl, logObject) {
            var userObject = logObject.embeds.user;
            if(paramIsReturned(userObject, logObject) && userObject.data) {
                return m('a', {href: userObject.data.links.html, onclick: function() {
                    $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-user-from-logs');
                }}, userObject.data.attributes.full_name);
            }
            else if (userObject && userObject.errors[0].meta) {
                return m('span', userObject.errors[0].meta.full_name);
            }
            else {
                return m('span', 'A user');
            }
        }
    },
    // Node involved
    node: {
        view: function (ctrl, logObject) {
            var nodeObject = logObject.embeds.nodes;

            if (logObject.attributes.action === 'node_removed') {
                if (logObject.attributes.params.params_node) {
                return m('span', logObject.attributes.params.params_node.title);
            }}
            else if(paramIsReturned(nodeObject, logObject) && nodeObject.data[0]){
                if (nodeObject.data[0].links && nodeObject.data[0].attributes) {
                    return m('a', {href: nodeObject.data[0].links.html, onclick: function() {
                        $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-project-from-logs');
                    }}, nodeObject.data[0].attributes.title);
                }
                else if (nodeObject.data[0].attributes) {
                    return m('span', nodeObject.data[0].attributes.title);
                }
            } else {
                return m('span', 'a project');
            }
        }
    },
    // Contrubutor list of added, updated etc.
    contributors: {
        view: function (ctrl, logObject) {
            var contributors = logObject.embeds.contributors;
            if(paramIsReturned(contributors, logObject)) {
                return m('span', contributors.data.map(function(item, index, arr){
                    var comma = ' ';
                    if(index !== arr.length - 1){
                        comma = ', ';
                    }
                    if(index === arr.length-2){
                        comma = ' and ';
                    }
                    if (item.attributes.active) {
                        return [ m('a', {href: item.links.html}, item.attributes.full_name), comma];
                    }
                    else {
                        return [item.attributes.full_name, comma];

                    }
                }));
            }
            return m('span', 'some users');
        }
    },
    // The tag added to item involved
    tag: {
        view: function (ctrl, logObject) {
            var tag = logObject.attributes.params.tag;
            if(paramIsReturned(tag, logObject)) {
               return m('a', {href: '/search/?q=%22' + tag + '%22'}, tag);
            }
            return m('span', 'a tag');
       }
    },
    // Node that is linked to the node involved
    pointer: {
        view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('a', {href: linked_node.data.links.html}, linked_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // Node that acted as template to create a new node involved
    template: {
        view: function (ctrl, logObject) {
            var template_node = logObject.embeds.template_node;
            if(paramIsReturned(template_node, logObject)){
                return m('a', {href: template_node.data.links.html}, template_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // The original title of node involved
    title_original: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_original', 'a title', logObject);
        }
    },
    // The new title of node involved
    title_new: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_new', 'a title', logObject);
        }
    },
    // Update fields for the node
    updated_fields: {
        view: function (ctrl, logObject) {
            return returnTextParams('updated_fields', 'field(s)', logObject);
        }
    },
    // external identifiers on node
    identifiers: {
        view: function (ctrl, logObject) {
            return returnTextParams('identifiers', 'identifier(s)', logObject);
        }
    },
    // Wiki page name
    page: {
        view: function (ctrl, logObject) {
            return returnTextParams('page', 'a title', logObject);
        }
    },
    // Old wiki title that's renamed
    old_page: {
        view: function (ctrl, logObject) {
            return returnTextParams('old_page', 'a title', logObject);
        }
    },
    // Wiki page version
    version: {
        view: function (ctrl, logObject) {
            var version = logObject.attributes.params.version;
            if(paramIsReturned(version, logObject)){
                return m('span', version);
            }
            return m('span', '#');
        }
    },
    //
    source: {
        view: function (ctrl, logObject) {
            var source = logObject.attributes.params.source;
            if(paramIsReturned(source, logObject)){
                return m('span', [source.materialized, ' in ', source.addon]);
            }
            return m('span','a name/location' );
        }
    },
    //
    destination: {
        view: function (ctrl, logObject) {
            var destination = logObject.attributes.params.destination;
            if(paramIsReturned(destination, logObject)){
                if (destination.materialized.endsWith('/')){
                    return m('span', [destination.materialized, ' in ', destination.addon]);
                }
                return m('span', [m('a', {href: destination.url}, destination.materialized), ' in ', destination.addon]);
            }
            return m('span','a new name/location' );
        }
    },
        //
    path: {
        view: function (ctrl, logObject) {
            return returnTextParams('path', 'a file', logObject);
        }
    },
        //
    filename: {
        view: function (ctrl, logObject) {
            return returnTextParams('filename', 'a title', logObject);
        }
    },

    folder: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder', 'a folder', logObject);
        }
    },

    repo: {
        view: function(ctrl, logObject) {
            var github_user = logObject.attributes.params.github_user;
            var github_repo = logObject.attributes.params.github_repo;
            if (paramIsReturned(github_repo, logObject) && paramIsReturned(github_user, logObject)){
                return m('span', github_user + '/' + github_repo);
            }
            return m('span', '');
        }
    },

    folder_name: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder_name', 'a folder', logObject);
        }
    },

    bucket: {
        view: function(ctrl, logObject) {
            return returnTextParams('bucket', 'a bucket', logObject);
        }
    },

    forward_url: {
        view: function(ctrl, logObject) {
            return returnTextParams('forward_url', 'a new URL', logObject);
        }
    },

    box_folder: {
        view: function(ctrl, logObject) {
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === 'All Files' ? '/ (Full Box)' : (folder || '').replace('All Files',''));
            }
            return m('span', '');
        }
    },

    citation: {
        view: function(ctrl, logObject) {
            return returnTextParams('citation_name', '', logObject);
        }
    },

    dataset: {
        view: function(ctrl, logObject){
            return returnTextParams('data_set', '', logObject);
        }
    },

    study: {
        view: function(ctrl, logObject){
            return returnTextParams('study', '', logObject);
        }
    },

    googledrive_path: {
        view: function(ctrl, logObject){
            var path = logObject.attributes.params.path;
            if(paramIsReturned(path, logObject)){
                return m('span', decodeURIComponent(path));
            }
            return m('span', '');
        }
    },

    googledrive_folder: {
        view: function(ctrl, logObject){
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === '/' ? '(Full Google Drive)' : decodeURIComponent(folder));
            }
            return m('span', '');
        }
    },

    addon: {
        view: function(ctrl, logObject){
            return returnTextParams('addon', '', logObject);
        }
    },

    previous_institution: {
        view: function(ctrl, logObject){
            var previous_institution = logObject.attributes.params.previous_institution;
            if (paramIsReturned(previous_institution, logObject)){
                return m('span', previous_institution.name);
            }
            return m('span', 'an institution');
        }
    },

    institution: {
        view: function(ctrl, logObject){
            var institution = logObject.attributes.params.institution;
            if (paramIsReturned(institution, logObject)){
                return m('span', institution.name);
            }
            return m('span', 'an institution');
        }
    },

    comment_file: {
        view: function(ctrl,logObject){
            var file = logObject.attributes.params.file;
            if (file){  // skipe paramIsReturned, as not having a file is expected at times
                return m('span', ['in ', m('a', {href: file.url}, file.name)]);
            }
            return m('span', '');
        }
    }
};

module.exports = LogText;
<commit_msg>Add function to controller that checks if user is present.  If no user, looks for alternative log text.<commit_after>/**
 * Parses text to return to the log items
 * Created by cos-caner on 12/4/15.
 * Remember to embed nodes, user, linked_node and template_node in api call i.e. var url = $osf.apiV2Url('nodes/' + nodeId + '/logs/', { query : { 'embed' : ['nodes', 'user']}});
 */
var m = require('mithril'); // exposes mithril methods, useful for redraw etc.
var logActions = require('json!js/_allLogTexts.json');
var $ = require('jquery');  // jQuery
var $osf = require('js/osfHelpers');

var ravenMessagesCache = []; // Cache messages to avoid sending multiple times in one page view
/**
 * Utility function to not repeat logging errors to Sentry
 * @param message {String} Custom message for error
 * @param logObject {Object} the entire log object returned from the api
 */
function ravenMessage (message, logObject) {
    if(ravenMessagesCache.indexOf(message) === -1){
        Raven.captureMessage(message, {logObject: logObject});
        ravenMessagesCache.push(message);
    }
}

/**
 * Checks if the required parameter to complete the log is returned
 * This may intentionally not be returned to make log anonymous
 * @param param {string|number} The parameter to be used
 * @param logObject {Object} the entire log object returned from the api
 * @returns {boolean}
 */
var paramIsReturned = function _paramIsReturned (param, logObject){
    if(!param){
        var message = 'Expected parameter for Log action ' + logObject.attributes.action + ' was not returned from log api.';
        ravenMessage(message, logObject);
        return false;
    }
    if (param.errors){
        return false;
    }
    return true;
};

/**
 * Returns the text parameters since their formatting is mainly the same
 * @param param {string} The parameter to be used, has to be available under logObject.attributes.param
 * @param text {string} The text to be used if param is not available
 * @param logObject {Object} the entire log object returned from the api
 * @returns {*}
 */
var returnTextParams = function (param, text, logObject) {
    var source = logObject.attributes.params[param];

    if(paramIsReturned(source, logObject)){
        if($.isArray(source)){
            return m('span', [
                source.map(function(item, index, arr){
                    if(arr.length === 1 && index === 0){
                        return m('span', item);
                    }
                    if(index === arr.length-1) {
                        return m('span', ', and ' + item);
                    }
                    return m('span', item + ', ');
                })
            ]);
        }
        return m('span', '"' + source + '"');
    }
    return m('span', text);
};

var LogText = {
    controller: function(logObject){
        var self = this;

        self.logText = function() {
            var text = logActions[logObject.attributes.action];
            if (text) {
                if (text.indexOf('${user}') !== -1) {
                    var userObject = logObject.embeds.user;
                    if (paramIsReturned(userObject, logObject)) {
                        return text;
                    }
                    else {
                        var newAction = logObject.attributes.action + '_no_user';
                        return logActions[newAction] ? logActions[newAction]: text;
                    }
                }
                return text;
            }
        return null;
        };
    },
    view : function(ctrl, logObject) {
        var message = '';
        var text = ctrl.logText();
        if(text){
            var list = text.split(/(\${.*?})/);
            return m('span.osf-log-item',[
                list.map(function(piece){
                    if (piece === '') {
                        return m('span');
                    }
                    var startsWith = piece.substring(0,2);
                    if(startsWith === '${'){
                        var last = piece.length-1;
                        var logComponentName = piece.substring(2,last);
                        if(LogPieces[logComponentName]){
                            return m.component(LogPieces[logComponentName], logObject);
                        } else {
                            message = 'There is no template in logTextParser.js for  ' + logComponentName + '.';
                            ravenMessage(message, logObject);
                            return m('');
                        }
                    }
                    return piece;
                })
            ]);
        } else {
            message = 'There is no text entry in dictionary for the action :' + logObject.attributes.action;
            ravenMessage(message, logObject);
            return m('');
        }
    }
};

var LogPieces = {
    // User that took the action
    user: {
        view: function (ctrl, logObject) {
            var userObject = logObject.embeds.user;
            if(paramIsReturned(userObject, logObject) && userObject.data) {
                return m('a', {href: userObject.data.links.html, onclick: function() {
                    $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-user-from-logs');
                }}, userObject.data.attributes.full_name);
            }
            else if (userObject && userObject.errors[0].meta) {
                return m('span', userObject.errors[0].meta.full_name);
            }
            else {
                return m('span', 'A user');
            }
        }
    },
    // Node involved
    node: {
        view: function (ctrl, logObject) {
            var nodeObject = logObject.embeds.nodes;

            if (logObject.attributes.action === 'node_removed') {
                if (logObject.attributes.params.params_node) {
                return m('span', logObject.attributes.params.params_node.title);
            }}
            else if(paramIsReturned(nodeObject, logObject) && nodeObject.data[0]){
                if (nodeObject.data[0].links && nodeObject.data[0].attributes) {
                    return m('a', {href: nodeObject.data[0].links.html, onclick: function() {
                        $osf.trackClick(logObject.trackingCategory, logObject.trackingAction, 'navigate-to-project-from-logs');
                    }}, nodeObject.data[0].attributes.title);
                }
                else if (nodeObject.data[0].attributes) {
                    return m('span', nodeObject.data[0].attributes.title);
                }
            } else {
                return m('span', 'a project');
            }
        }
    },
    // Contrubutor list of added, updated etc.
    contributors: {
        view: function (ctrl, logObject) {
            var contributors = logObject.embeds.contributors;
            if(paramIsReturned(contributors, logObject)) {
                return m('span', contributors.data.map(function(item, index, arr){
                    var comma = ' ';
                    if(index !== arr.length - 1){
                        comma = ', ';
                    }
                    if(index === arr.length-2){
                        comma = ' and ';
                    }
                    if (item.attributes.active) {
                        return [ m('a', {href: item.links.html}, item.attributes.full_name), comma];
                    }
                    else {
                        return [item.attributes.full_name, comma];

                    }
                }));
            }
            return m('span', 'some users');
        }
    },
    // The tag added to item involved
    tag: {
        view: function (ctrl, logObject) {
            var tag = logObject.attributes.params.tag;
            if(paramIsReturned(tag, logObject)) {
               return m('a', {href: '/search/?q=%22' + tag + '%22'}, tag);
            }
            return m('span', 'a tag');
       }
    },
    // Node that is linked to the node involved
    pointer: {
        view: function (ctrl, logObject) {
            var linked_node = logObject.embeds.linked_node;
            if(paramIsReturned(linked_node, logObject)){
                return m('a', {href: linked_node.data.links.html}, linked_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // Node that acted as template to create a new node involved
    template: {
        view: function (ctrl, logObject) {
            var template_node = logObject.embeds.template_node;
            if(paramIsReturned(template_node, logObject)){
                return m('a', {href: template_node.data.links.html}, template_node.data.attributes.title);
            }
            return m('span','a project' );
        }
    },
    // The original title of node involved
    title_original: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_original', 'a title', logObject);
        }
    },
    // The new title of node involved
    title_new: {
        view: function (ctrl, logObject) {
            return returnTextParams('title_new', 'a title', logObject);
        }
    },
    // Update fields for the node
    updated_fields: {
        view: function (ctrl, logObject) {
            return returnTextParams('updated_fields', 'field(s)', logObject);
        }
    },
    // external identifiers on node
    identifiers: {
        view: function (ctrl, logObject) {
            return returnTextParams('identifiers', 'identifier(s)', logObject);
        }
    },
    // Wiki page name
    page: {
        view: function (ctrl, logObject) {
            return returnTextParams('page', 'a title', logObject);
        }
    },
    // Old wiki title that's renamed
    old_page: {
        view: function (ctrl, logObject) {
            return returnTextParams('old_page', 'a title', logObject);
        }
    },
    // Wiki page version
    version: {
        view: function (ctrl, logObject) {
            var version = logObject.attributes.params.version;
            if(paramIsReturned(version, logObject)){
                return m('span', version);
            }
            return m('span', '#');
        }
    },
    //
    source: {
        view: function (ctrl, logObject) {
            var source = logObject.attributes.params.source;
            if(paramIsReturned(source, logObject)){
                return m('span', [source.materialized, ' in ', source.addon]);
            }
            return m('span','a name/location' );
        }
    },
    //
    destination: {
        view: function (ctrl, logObject) {
            var destination = logObject.attributes.params.destination;
            if(paramIsReturned(destination, logObject)){
                if (destination.materialized.endsWith('/')){
                    return m('span', [destination.materialized, ' in ', destination.addon]);
                }
                return m('span', [m('a', {href: destination.url}, destination.materialized), ' in ', destination.addon]);
            }
            return m('span','a new name/location' );
        }
    },
        //
    path: {
        view: function (ctrl, logObject) {
            return returnTextParams('path', 'a file', logObject);
        }
    },
        //
    filename: {
        view: function (ctrl, logObject) {
            return returnTextParams('filename', 'a title', logObject);
        }
    },

    folder: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder', 'a folder', logObject);
        }
    },

    repo: {
        view: function(ctrl, logObject) {
            var github_user = logObject.attributes.params.github_user;
            var github_repo = logObject.attributes.params.github_repo;
            if (paramIsReturned(github_repo, logObject) && paramIsReturned(github_user, logObject)){
                return m('span', github_user + '/' + github_repo);
            }
            return m('span', '');
        }
    },

    folder_name: {
        view: function(ctrl, logObject) {
            return returnTextParams('folder_name', 'a folder', logObject);
        }
    },

    bucket: {
        view: function(ctrl, logObject) {
            return returnTextParams('bucket', 'a bucket', logObject);
        }
    },

    forward_url: {
        view: function(ctrl, logObject) {
            return returnTextParams('forward_url', 'a new URL', logObject);
        }
    },

    box_folder: {
        view: function(ctrl, logObject) {
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === 'All Files' ? '/ (Full Box)' : (folder || '').replace('All Files',''));
            }
            return m('span', '');
        }
    },

    citation: {
        view: function(ctrl, logObject) {
            return returnTextParams('citation_name', '', logObject);
        }
    },

    dataset: {
        view: function(ctrl, logObject){
            return returnTextParams('data_set', '', logObject);
        }
    },

    study: {
        view: function(ctrl, logObject){
            return returnTextParams('study', '', logObject);
        }
    },

    googledrive_path: {
        view: function(ctrl, logObject){
            var path = logObject.attributes.params.path;
            if(paramIsReturned(path, logObject)){
                return m('span', decodeURIComponent(path));
            }
            return m('span', '');
        }
    },

    googledrive_folder: {
        view: function(ctrl, logObject){
            var folder = logObject.attributes.params.folder;
            if(paramIsReturned(folder, logObject)){
                return m('span', folder === '/' ? '(Full Google Drive)' : decodeURIComponent(folder));
            }
            return m('span', '');
        }
    },

    addon: {
        view: function(ctrl, logObject){
            return returnTextParams('addon', '', logObject);
        }
    },

    previous_institution: {
        view: function(ctrl, logObject){
            var previous_institution = logObject.attributes.params.previous_institution;
            if (paramIsReturned(previous_institution, logObject)){
                return m('span', previous_institution.name);
            }
            return m('span', 'an institution');
        }
    },

    institution: {
        view: function(ctrl, logObject){
            var institution = logObject.attributes.params.institution;
            if (paramIsReturned(institution, logObject)){
                return m('span', institution.name);
            }
            return m('span', 'an institution');
        }
    },

    comment_file: {
        view: function(ctrl,logObject){
            var file = logObject.attributes.params.file;
            if (file){  // skipe paramIsReturned, as not having a file is expected at times
                return m('span', ['in ', m('a', {href: file.url}, file.name)]);
            }
            return m('span', '');
        }
    }
};

module.exports = LogText;
<|endoftext|>


<commit_before>var assert = require("assert");
var http = require("http");

describe('api', function(){
  describe('#getUrl()', function(done){
    it('should return the URL for the supplied slug', function(done){

		  http.get({
				host: 'www.tau.pe',
				path: '/url/GB2SL'
			},function(response){
				var data = "";

				response.on("data", function(chunk){
					if(chunk.length > 0) data += chunk;
				});

				response.on("end", function(){
					var url = '';
					if(data != null){
						var json = JSON.parse(data);
						if(json != null){
							url = json.url;
						}
					}
					assert.equal('http://softlayer.com/', url);
					done();
				});

			}).end();
		})
  })
})
<commit_msg>semicolons!<commit_after>var assert = require("assert");
var http = require("http");

describe('api', function(){
  describe('#getUrl()', function(done){
    it('should return the URL for the supplied slug', function(done){

		  http.get({
				host: 'www.tau.pe',
				path: '/url/GB2SL'
			},function(response){
				var data = "";

				response.on("data", function(chunk){
					if(chunk.length > 0) data += chunk;
				});

				response.on("end", function(){
					var url = '';
					if(data != null){
						var json = JSON.parse(data);
						if(json != null){
							url = json.url;
						}
					}
					assert.equal('http://softlayer.com/', url);
					done();
				});

			}).end();
		})
  })
});
<|endoftext|>


<commit_before>var options = {
    ".local": {
        "title": "[DEV] {title}"
    },
    "domain.com": {
        "title": "[PROD] {title}"
    },
    "youtube.com": {
        "icon": "https://www.google.com/favicon.ico",
        "pinned": true
    },
    "twitter.com": {
        "title": "I'm working hard!",
        "icon": "{default}",
        "protected": true
    },
    "mail.google.com": {
        "unique": true
    },
    "github.com": {
        "title": "[$1::$2] {title}",
        "url_matcher":  "github\\.com\/([A-Za-z0-9\\-\\_]+)\/([A-Za-z0-9\\-\\_]+)"
    }
};

var tab1 = new Tab('http://project.local.domain.com', 'My project', options);

QUnit.test('http://project.local.domain.com should match ".local"', function (assert) {
    assert.strictEqual('.local', tab1.getMatch(), 'match OK');
    assert.strictEqual('[DEV] My project', tab1.getTitle(), 'title OK');
    assert.strictEqual(null, tab1.getIcon(), 'icon OK');
    assert.strictEqual(null, tab1.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab1.getProtected(), 'protected OK');
    assert.strictEqual(null, tab1.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab2 = new Tab('http://shop.domain.com', 'My project', options);

QUnit.test('http://shop.domain.com should match "domain.com"', function (assert) {
    assert.strictEqual('domain.com', tab2.getMatch(), 'match OK');
    assert.strictEqual('[PROD] My project', tab2.getTitle(), 'title OK');
    assert.strictEqual(null, tab2.getIcon(), 'icon OK');
    assert.strictEqual(null, tab2.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab2.getProtected(), 'protected OK');
    assert.strictEqual(null, tab2.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab3 = new Tab('http://youtube.com', 'My project', options);

QUnit.test('http://youtube.com should match "youtube.com"', function (assert) {
    assert.strictEqual('youtube.com', tab3.getMatch(), 'match OK');
    assert.strictEqual(null, tab3.getTitle(), 'title OK');
    assert.strictEqual('https://www.google.com/favicon.ico', tab3.getIcon(), 'icon OK');
    assert.strictEqual(true, tab3.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab3.getProtected(), 'protected OK');
    assert.strictEqual(null, tab3.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab4 = new Tab('http://twitter.com', 'My project', options);

QUnit.test('http://twitter.com should match "twitter.com"', function (assert) {
    assert.strictEqual('twitter.com', tab4.getMatch(), 'match OK');
    assert.strictEqual('I\'m working hard!', tab4.getTitle(), 'title OK');
    assert.strictEqual('{default}', tab4.getIcon(), 'icon OK');
    assert.strictEqual(null, tab4.getPinned(), 'pinned OK');
    assert.strictEqual(true, tab4.getProtected(), 'protected OK');
    assert.strictEqual(null, tab4.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab5 = new Tab('https://mail.google.com/mail/u/0/#inbox', 'My project', options);

QUnit.test('https://mail.google.com/mail/u/0/#inbox should match "mail.google.com"', function (assert) {
    assert.strictEqual('mail.google.com', tab5.getMatch(), 'match OK');
    assert.strictEqual(null, tab5.getTitle(), 'title OK');
    assert.strictEqual(null, tab5.getIcon(), 'icon OK');
    assert.strictEqual(null, tab5.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab5.getProtected(), 'protected OK');
    assert.strictEqual(true, tab5.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab6 = new Tab('https://github.com/user-name/repo_name/pulls/123', 'My project', options);

QUnit.test('https://github.com/user-name/repo_name/pulls/123 should match "github.com"', function (assert) {
    assert.strictEqual('github.com', tab6.getMatch(), 'match OK');
    assert.strictEqual('[user-name::repo_name] My project', tab6.getTitle(), 'title OK');
    assert.strictEqual(null, tab6.getIcon(), 'icon OK');
    assert.strictEqual(null, tab6.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab6.getProtected(), 'protected OK');
    assert.strictEqual(null, tab6.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab7 = new Tab('http://not-found.com', 'My project', options);

QUnit.test('http://not-found.com should not match anything', function (assert) {
    assert.strictEqual(null, tab7.getMatch(), 'match OK');
    assert.strictEqual(null, tab7.getTitle(), 'title OK');
    assert.strictEqual(null, tab7.getIcon(), 'icon OK');
    assert.strictEqual(null, tab7.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab7.getProtected(), 'protected OK');
    assert.strictEqual(null, tab7.getUnique(), 'unique OK');
});
<commit_msg>fixed tests<commit_after>var options = {
    ".local": {
        "title": "[DEV] {title}"
    },
    "domain.com": {
        "title": "[PROD] {title}"
    },
    "youtube.com": {
        "icon": "https://www.google.com/favicon.ico",
        "pinned": true
    },
    "twitter.com": {
        "title": "I'm working hard!",
        "icon": "{default}",
        "protected": true
    },
    "mail.google.com": {
        "unique": true
    },
    "github.com": {
        "title": "[$1::$2] {title}",
        "url_matcher":  "github\\.com\/([A-Za-z0-9\\-\\_]+)\/([A-Za-z0-9\\-\\_]+)"
    }
};

var tab1 = new Tab('http://project.local.domain.com', 'My project', options);

tab1.setTitle();

QUnit.test('http://project.local.domain.com should match ".local"', function (assert) {
    assert.strictEqual('.local', tab1.getMatch(), 'match OK');
    assert.strictEqual('[DEV] My project', tab1.getTitle(), 'title OK');
    assert.strictEqual(null, tab1.getIcon(), 'icon OK');
    assert.strictEqual(null, tab1.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab1.getProtected(), 'protected OK');
    assert.strictEqual(null, tab1.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab2 = new Tab('http://shop.domain.com', 'My project', options);

tab2.setTitle();

QUnit.test('http://shop.domain.com should match "domain.com"', function (assert) {
    assert.strictEqual('domain.com', tab2.getMatch(), 'match OK');
    assert.strictEqual('[PROD] My project', tab2.getTitle(), 'title OK');
    assert.strictEqual(null, tab2.getIcon(), 'icon OK');
    assert.strictEqual(null, tab2.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab2.getProtected(), 'protected OK');
    assert.strictEqual(null, tab2.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab3 = new Tab('http://youtube.com', 'My project', options);

tab3.setTitle();

QUnit.test('http://youtube.com should match "youtube.com"', function (assert) {
    assert.strictEqual('youtube.com', tab3.getMatch(), 'match OK');
    assert.strictEqual(null, tab3.getTitle(), 'title OK');
    assert.strictEqual('https://www.google.com/favicon.ico', tab3.getIcon(), 'icon OK');
    assert.strictEqual(true, tab3.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab3.getProtected(), 'protected OK');
    assert.strictEqual(null, tab3.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab4 = new Tab('http://twitter.com', 'My project', options);

tab4.setTitle();

QUnit.test('http://twitter.com should match "twitter.com"', function (assert) {
    assert.strictEqual('twitter.com', tab4.getMatch(), 'match OK');
    assert.strictEqual('I\'m working hard!', tab4.getTitle(), 'title OK');
    assert.strictEqual('{default}', tab4.getIcon(), 'icon OK');
    assert.strictEqual(null, tab4.getPinned(), 'pinned OK');
    assert.strictEqual(true, tab4.getProtected(), 'protected OK');
    assert.strictEqual(null, tab4.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab5 = new Tab('https://mail.google.com/mail/u/0/#inbox', 'My project', options);

tab5.setTitle();

QUnit.test('https://mail.google.com/mail/u/0/#inbox should match "mail.google.com"', function (assert) {
    assert.strictEqual('mail.google.com', tab5.getMatch(), 'match OK');
    assert.strictEqual(null, tab5.getTitle(), 'title OK');
    assert.strictEqual(null, tab5.getIcon(), 'icon OK');
    assert.strictEqual(null, tab5.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab5.getProtected(), 'protected OK');
    assert.strictEqual(true, tab5.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab6 = new Tab('https://github.com/user-name/repo_name/pulls/123', 'My project', options);

tab6.setTitle();

QUnit.test('https://github.com/user-name/repo_name/pulls/123 should match "github.com"', function (assert) {
    assert.strictEqual('github.com', tab6.getMatch(), 'match OK');
    assert.strictEqual('[user-name::repo_name] My project', tab6.getTitle(), 'title OK');
    assert.strictEqual(null, tab6.getIcon(), 'icon OK');
    assert.strictEqual(null, tab6.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab6.getProtected(), 'protected OK');
    assert.strictEqual(null, tab6.getUnique(), 'unique OK');
});

// ----------------------------------------------------------

var tab7 = new Tab('http://not-found.com', 'My project', options);

tab7.setTitle();

QUnit.test('http://not-found.com should not match anything', function (assert) {
    assert.strictEqual(null, tab7.getMatch(), 'match OK');
    assert.strictEqual(null, tab7.getTitle(), 'title OK');
    assert.strictEqual(null, tab7.getIcon(), 'icon OK');
    assert.strictEqual(null, tab7.getPinned(), 'pinned OK');
    assert.strictEqual(null, tab7.getProtected(), 'protected OK');
    assert.strictEqual(null, tab7.getUnique(), 'unique OK');
});
<|endoftext|>


<commit_before>/* eslint-disable no-undef */
const fs = require('fs-extra');
const rename = require('../rename');

// remove test directory
fs.removeSync('./test');
// create test files/directories
fs.ensureDirSync('test');
fs.writeFileSync('test/one.txt', 'file one', 'utf8');
fs.writeFileSync('test/two.txt', 'file two', 'utf8');
// run tests
describe('Simple Tests');
<commit_msg>another test<commit_after>/* eslint-disable no-undef */
const assert = require('assert');
const fs = require('fs-extra');
const rename = require('../rename');

// remove test directory
fs.removeSync('./test');
// create test files/directories
fs.ensureDirSync('test');
fs.writeFileSync('test/one.txt', 'file one', 'utf8');
fs.writeFileSync('test/two.txt', 'file two', 'utf8');
// run tests
describe('Simple Tests', function() {
  it('should always be true', function() {
    assert.equal(1,1);
  });
});
<|endoftext|>


<commit_before>'use strict';
// tests to write:
// 6 week mo 8/2015
// 5 week mo 10/2015
// 4 week mo 2/2015
// 30 day month 11/2015
// 31 day month 12/2015
// Fed Leap year 2/2012
// Feb non leap 2/2014
// Some bad years e.g. 'a', 1000, 100000, -2 etc.
//
//
const { expect } = require('chai');
const { execSync } = require('child_process');

describe('cal', () => {
  describe('CLI', () => {
    it('should handle the current month', () => {
      const goal = execSync('cal').toString();
      const output = execSync('node --harmony_destructuring ./cal.js').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 6 week month', () => {
      const goal = execSync('cal 8 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 8').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 5 week month', () => {
      const goal = execSync('cal 10 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 10').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 4 week month', () => {
      const goal = execSync('cal 2 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 30 day month 11/2015', () => {
      const goal = execSync('cal 11 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 11').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 31 day month 12/2015', () => {
      const goal = execSync('cal 12 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 12').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a leap year 2/2012', () => {
      const goal = execSync('cal 2 2012').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2012 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a non leap year 2/2014', () => {
      const goal = execSync('cal 2 2014').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2014 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a full year', () => {
      const goal = execSync('cal 2014').toString()
      const output = execSync('node --harmony_destructuring ./cal.js 2014').toString()
      // expect(output.join("")).to.equal(goal.join(""));
      expect(output).to.equal(goal);
    });

    it('should handle a full year', () => {
      const goal = execSync('cal 1886').toString()
      const output = execSync('node --harmony_destructuring ./cal.js 1886').toString()
      // expect(output.join("")).to.equal(goal.join(""));
      expect(output).to.equal(goal);
    });

  });

  describe("Validate args", () => {
    const validateArgs = require('../lib/validateArgs.js');
    const currentDate = new Date();
    const compareArr = [currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate()];

    it('return the current date when no arguments are passed', () => {
      expect(validateArgs.validate([])[0]).to.equal(compareArr[0]);
      expect(validateArgs.validate([])[1]).to.equal(compareArr[1]);
      expect(validateArgs.validate([])[2]).to.equal(compareArr[2]);
    });

    it('return the formatted date when a correct argument is passed', () => {
      expect(validateArgs.validate([2015,2])[0]).to.equal(2015);
      expect(validateArgs.validate([2015,2])[1]).to.equal(2);
    });

    it('return just the year when only 1 argument is passed', () => {
      expect(validateArgs.validate([2015])[0]).to.equal(2015);
    });

    it('return null for too many arguments', () => {
       expect(validateArgs.validate([2,2,3,4])).to.be.a('null');
    });

    it('return null for invalid arguments', () => {
      expect(validateArgs.validate(['a'])).to.be.a('null');
      expect(validateArgs.validate([-1])).to.be.a('null');
      expect(validateArgs.validate([1600, 3])).to.be.a('null');
      expect(validateArgs.validate([100000, 3])).to.be.a('null');
      expect(validateArgs.validate([1600, 3])).to.be.a('null');

    });

  });

  describe("Zeller's congruence", () => {
    const zellers = require('../lib/zeller.js');

    describe('.modifiedMonth', () => {
      it('return 13 for January', () => {
        expect(zellers.modifiedMonth(1)).to.equal(13);
      });
      it('return 14 for February', () => {
        expect(zellers.modifiedMonth(2)).to.equal(14);
      });
      it('return 3 for March', () => {
        expect(zellers.modifiedMonth(3)).to.equal(3);
      });
    });

    describe('.modifiedYear', () => {
      it('returns 2014 for Jan 2015', () => {
        expect(zellers.modifiedYear(2015, 1)).to.equal(2014);
      });
      it('returns 2015 for Feb 2016', () => {
        expect(zellers.modifiedYear(2016, 2)).to.equal(2015);
      });
      it('returns 2017 for March 2017', () => {
        expect(zellers.modifiedYear(2017, 3)).to.equal(2017);
      });
    });

    describe('.getDay', () => {
      it('returns 5 (Friday) for January 1, 2016', () => {
        expect(zellers.getDay(2016, 1, 1)).to.equal(5);
      });
      it('returns 2 (Tuesday) for March 1, 2016', () => {
        expect(zellers.getDay(2016, 3, 1)).to.equal(2);
      });
       it('returns 3 (Wednesday) for March 1, 2000', () => {
        expect(zellers.getDay(2000, 3, 1)).to.equal(3);
      });
        it('returns 0 (Sunday) for March 2, 2200', () => {
        expect(zellers.getDay(2200, 3, 2)).to.equal(0);
      });
        it('returns 4 (Thursday) for March 1, 2300', () => {
        expect(zellers.getDay(2300, 3, 1)).to.equal(4);
      });
      // 2300, 3, 1 === 4
    });
  });
});
<commit_msg>rename test to specify year tested<commit_after>'use strict';
// tests to write:
// 6 week mo 8/2015
// 5 week mo 10/2015
// 4 week mo 2/2015
// 30 day month 11/2015
// 31 day month 12/2015
// Fed Leap year 2/2012
// Feb non leap 2/2014
// Some bad years e.g. 'a', 1000, 100000, -2 etc.
//
//
const { expect } = require('chai');
const { execSync } = require('child_process');

describe('cal', () => {
  describe('CLI', () => {
    it('should handle the current month', () => {
      const goal = execSync('cal').toString();
      const output = execSync('node --harmony_destructuring ./cal.js').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 6 week month', () => {
      const goal = execSync('cal 8 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 8').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 5 week month', () => {
      const goal = execSync('cal 10 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 10').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 4 week month', () => {
      const goal = execSync('cal 2 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 30 day month 11/2015', () => {
      const goal = execSync('cal 11 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 11').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a 31 day month 12/2015', () => {
      const goal = execSync('cal 12 2015').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2015 12').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a leap year 2/2012', () => {
      const goal = execSync('cal 2 2012').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2012 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a non leap year 2/2014', () => {
      const goal = execSync('cal 2 2014').toString();
      const output = execSync('node --harmony_destructuring ./cal.js 2014 2').toString();

      expect(output).to.equal(goal);
    });

    it('should handle a full year 2014', () => {
      const goal = execSync('cal 2014').toString()
      const output = execSync('node --harmony_destructuring ./cal.js 2014').toString()
      // expect(output.join("")).to.equal(goal.join(""));
      expect(output).to.equal(goal);
    });

    it('should handle a full year 1886', () => {
      const goal = execSync('cal 1886').toString()
      const output = execSync('node --harmony_destructuring ./cal.js 1886').toString()
      // expect(output.join("")).to.equal(goal.join(""));
      expect(output).to.equal(goal);
    });

  });

  describe("Validate args", () => {
    const validateArgs = require('../lib/validateArgs.js');
    const currentDate = new Date();
    const compareArr = [currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate()];

    it('return the current date when no arguments are passed', () => {
      expect(validateArgs.validate([])[0]).to.equal(compareArr[0]);
      expect(validateArgs.validate([])[1]).to.equal(compareArr[1]);
      expect(validateArgs.validate([])[2]).to.equal(compareArr[2]);
    });

    it('return the formatted date when a correct argument is passed', () => {
      expect(validateArgs.validate([2015,2])[0]).to.equal(2015);
      expect(validateArgs.validate([2015,2])[1]).to.equal(2);
    });

    it('return just the year when only 1 argument is passed', () => {
      expect(validateArgs.validate([2015])[0]).to.equal(2015);
    });

    it('return null for too many arguments', () => {
       expect(validateArgs.validate([2,2,3,4])).to.be.a('null');
    });

    it('return null for invalid arguments', () => {
      expect(validateArgs.validate(['a'])).to.be.a('null');
      expect(validateArgs.validate([-1])).to.be.a('null');
      expect(validateArgs.validate([1600, 3])).to.be.a('null');
      expect(validateArgs.validate([100000, 3])).to.be.a('null');
      expect(validateArgs.validate([1600, 3])).to.be.a('null');

    });

  });

  describe("Zeller's congruence", () => {
    const zellers = require('../lib/zeller.js');

    describe('.modifiedMonth', () => {
      it('return 13 for January', () => {
        expect(zellers.modifiedMonth(1)).to.equal(13);
      });
      it('return 14 for February', () => {
        expect(zellers.modifiedMonth(2)).to.equal(14);
      });
      it('return 3 for March', () => {
        expect(zellers.modifiedMonth(3)).to.equal(3);
      });
    });

    describe('.modifiedYear', () => {
      it('returns 2014 for Jan 2015', () => {
        expect(zellers.modifiedYear(2015, 1)).to.equal(2014);
      });
      it('returns 2015 for Feb 2016', () => {
        expect(zellers.modifiedYear(2016, 2)).to.equal(2015);
      });
      it('returns 2017 for March 2017', () => {
        expect(zellers.modifiedYear(2017, 3)).to.equal(2017);
      });
    });

    describe('.getDay', () => {
      it('returns 5 (Friday) for January 1, 2016', () => {
        expect(zellers.getDay(2016, 1, 1)).to.equal(5);
      });
      it('returns 2 (Tuesday) for March 1, 2016', () => {
        expect(zellers.getDay(2016, 3, 1)).to.equal(2);
      });
       it('returns 3 (Wednesday) for March 1, 2000', () => {
        expect(zellers.getDay(2000, 3, 1)).to.equal(3);
      });
        it('returns 0 (Sunday) for March 2, 2200', () => {
        expect(zellers.getDay(2200, 3, 2)).to.equal(0);
      });
        it('returns 4 (Thursday) for March 1, 2300', () => {
        expect(zellers.getDay(2300, 3, 1)).to.equal(4);
      });
      // 2300, 3, 1 === 4
    });
  });
});
<|endoftext|>


<commit_before>Tinytest.add("lodash - Exists", function(test) {
	test.equal(_, lodash, "Expected _ to be lodash");
	test.notEqual(typeof lodash, 'undefined', "Expected lodash to not be undefined");
});
<commit_msg>Removed 1 test<commit_after>Tinytest.add("lodash - Exists", function(test) {
	test.notEqual(typeof lodash, 'undefined', "Expected lodash to not be undefined");
});
<|endoftext|>


<commit_before>module.exports = function(profileInfo, browser, asyncCallback) {
	browser
		.evaluate(function() {
			var output = [];

			// Add each education node's information as a JSON
			// object to output.
			var educationNodes = document.querySelectorAll(
				'#background-education > div.entity-container > div.entity ' +
				' > div.edit-action-area');

			educationNodes.forEach(function(educationNode) {
				// Utility functions
				var get = (selector) => educationNode.querySelector(selector);
				var getText = function(selector) {
					var targetNode = get(selector);
					if (!targetNode) {
						return null;
					}
					return targetNode.innerText;
				};
				var getImgSrc = function(selector) {
					var targetNode = get(selector);
					if (!targetNode) {
						return null;
					}
					return targetNode.src;
				}
				var getAll = (selector) =>
					educationNode.querySelectorAll(selector);
				var clean = function(str) {
					if (!str) {
						return str;
					}
					return str.trim().replace(/(^[\W])|(,$)/g, '').trim();
				}

				// Extract basic details about this education entry.
				var educationInfo = {
					title: getText('div > header > h4 > div > span'),
					degree: clean(getText('div > header > h5 > span >' +
						' span.degree')),
					major: clean(getText('div > header > h5 > span >' +
						' span.major')),
					grade: getText('div > header > h5 > span >' +
						' span:nth-child(3)'),
					startTime: getText('div > div.date-header-field.' +
						'field > span > time:nth-child(1)'),
					endTime: clean(getText('div > div.date-header-field.' +
						'field > span > time:nth-child(2)')),
					description: getText('div > p > span'),
					imgUrl: getImgSrc('div > div > h5 > span > strong > img')
				};

				// Extract related projects and courses info for this
				// education entry.
				educationInfo.associatedProjects = [];
				getAll('div > dl > dd:nth-child(2) > ul > li > h6')
					.forEach(function(projectNode) {
						educationInfo.associatedProjects.push(
							projectNode.innerHTML);
					});

				const PREFIX = 'div > dl > dd:nth-child(';
				const SUFFIX = ') > ul > li > h6';

				var courseNodes = getAll(PREFIX + 6 + SUFFIX);
				var awardNodes = getAll(PREFIX + 4 + SUFFIX);
				if (courseNodes.length === 0) {
					courseNodes = getAll(PREFIX + 4 + SUFFIX);
					awardNodes = [];
				}

				educationInfo.associatedCourses = [];
				courseNodes.forEach(function(courseNode) {
					educationInfo.associatedCourses.push(courseNode.innerHTML);
				});

				educationInfo.associatedAwards = [];
				awardNodes.forEach(function(awardNode) {
					educationInfo.associatedAwards.push(awardNode.innerHTML);
				});

				// Add JSON object with current education entry's
				// details to output.
				output.push(educationInfo);
			});

			// Return a list of JSON objects, where each object is
			// essentially a LinkedIn education entry.
			return output;
		})
		.then(function(educationInfo) {
			profileInfo.education = educationInfo;
			asyncCallback(null, profileInfo, browser);
		}, function() {
			profileInfo.education = [];
			profileInfo.errors.push('Unable to access education section.');
			asyncCallback(null, profileInfo, browser);
		});
};<commit_msg>Fixed get-education for others' profiles.<commit_after>module.exports = function(profileInfo, browser, asyncCallback) {
	browser
		.evaluate(function() {
			var output = [];

			// Add each education node's information as a JSON
			// object to output.
			var educationNodes = document.querySelectorAll([
				'#background-education > div.entity-container >',
				'div.entity > div.edit-action-area'
			].join(' '));
			if (educationNodes.length === 0) {
				educationNodes = document.querySelectorAll(
					'#background-education > div > div > div');
			}

			educationNodes.forEach(function(educationNode) {
				// Utility functions
				var get = function(selector, altSelector) {
					return educationNode.querySelector(selector) ||
						educationNode.querySelector(altSelector);
				};

				var getText = function(selector, altSelector) {
					var targetNode = get(selector, altSelector);
					if (!targetNode) {
						return null;
					}
					return targetNode.innerText;
				};

				var getImgSrc = function(selector, altSelector) {
					var targetNode = get(selector, altSelector);
					if (!targetNode) {
						return null;
					}
					return targetNode.src;
				};

				// For the first selector with matches, return the
				// corresponding list of nodes.
				var getAll = function(selector) {
					var target = educationNode.querySelectorAll(selector);
					if (arguments.length === 1 || target.length > 0) {
						return target;
					}

					for (var i = 1; i < arguments.length; i++) {
						var arg = arguments[i];
						target = educationNode.querySelectorAll(arg);

						// We're done if target has items because we
						// found matches for this selector, arg.
						if (arg && target && target.length > 0) {
							break;
						}
					}
					return target;
				};

				var clean = function(str) {
					if (!str) {
						return str;
					}
					return str.trim().replace(/(^[\W])|(,$)/g, '').trim();
				};

				// Extract basic details about this education entry.
				var educationInfo = {
					title: getText('div > header > h4 > div > span',
						'div > header > h4 > a'),
					degree: clean(getText('div > header > h5 > span >' +
						' span.degree', 'div > header > h5 > span.degree')),
					major: clean(getText('div > header > h5 > span >' +
						' span.major', 'div > header > h5 > span.major > a')),
					grade: getText('div > header > h5 > span >' +
						' span:nth-child(3)', 'div > header > h5 > span.grade'),
					startTime: getText('div > div.date-header-field.' +
						'field > span > time:nth-child(1)',
						'div > span > time:nth-child(1)'),
					endTime: clean(getText('div > div.date-header-field.' +
						'field > span > time:nth-child(2)',
						'div > span > time:nth-child(2)')),
					description: getText('div > p > span', 'div > p'),
					imgUrl: getImgSrc('div > div > h5 > span > strong > img',
						'div > a > h5 > span > strong > img')
				};

				// Extract related projects and courses info for this
				// education entry.
				educationInfo.associatedProjects = [];
				getAll('div > dl > dd:nth-child(2) > ul > li > h6')
					.forEach(function(projectNode) {
						educationInfo.associatedProjects.push(
							projectNode.innerText);
					});

				const PREFIX = 'div > dl > dd:nth-child(';
				const SUFFIX = ') > ul > li';
				const OPTIONAL_SUFFIX = ' > h6';

				var courseNodes = getAll(PREFIX + 6 + SUFFIX,
					PREFIX + 6 + SUFFIX + OPTIONAL_SUFFIX);
				var awardNodes = getAll(PREFIX + 4 + SUFFIX);
				if (courseNodes.length === 0) {
					courseNodes = getAll(PREFIX + 4 + SUFFIX,
						PREFIX + 4 + SUFFIX + OPTIONAL_SUFFIX,
						'div > dl > dd > ul > li');
					awardNodes = [];
				}

				educationInfo.associatedCourses = [];
				courseNodes.forEach(function(courseNode) {
					educationInfo.associatedCourses.push(courseNode.innerText);
				});

				educationInfo.associatedAwards = [];
				awardNodes.forEach(function(awardNode) {
					educationInfo.associatedAwards.push(awardNode.innerText);
				});

				// Add JSON object with current education entry's
				// details to output.
				output.push(educationInfo);
			});

			// Return a list of JSON objects, where each object is
			// essentially a LinkedIn education entry.
			return output;
		})
		.then(function(educationInfo) {
			profileInfo.education = educationInfo;
			asyncCallback(null, profileInfo, browser);
		}, function() {
			profileInfo.education = [];
			profileInfo.errors.push('Unable to access education section.');
			asyncCallback(null, profileInfo, browser);
		});
};<|endoftext|>


<commit_before>import test from 'ava';

test('Changing colors', t => {
  t.pass("Testing");
});

test('Changing date format', t => {
  t.pass("Testing");
});

test('Removing log type', t => {
  t.pass("Testing");
});

test('Removing timestamp', t => {
  t.pass("Testing");
});
<commit_msg>Much testing<commit_after>import test from 'ava';

test('Changing colors', t => {
  t.pass("Testing");
});

test('Changing date format', t => {
  t.pass("Testing");
});

test('Removing log type', t => {
  t.pass("Testing");
});

test('Removing timestamp', t => {
  t.pass("Testing");
});

test('Resetting everything', t => {
  t.pass("Testing");
});
<|endoftext|>


<commit_before>// NotePresenter class used to draw a note called in appendNotes
PinPoint.NotePresenter = function(note, url, refreshFunc) {
  this.note = note;
  this.url = url;
  this.refreshFunc = refreshFunc;
  this.nodeType = "div";
  this.linkNodeType = "a";
  this.buttonNodeType = "button";
}

PinPoint.NotePresenter.prototype = {
  present: function() {
    // Creates a 'div' node that represents a note
    var noteNode = document.createElement(this.nodeType),
    // Creates a 'div' node that represents the time and delete button in the note - when var is included, errors are thrown for some reason.
    timeAndDeleteNode = document.createElement(this.nodeType),
    // Creates an 'a' node to link to a point in the video - when var is included, errors are thrown for some reason.
    timeLink = document.createElement(this.linkNodeType),
    //Creates delete link
    // deleteNode = document.createElement(this.childNodeType);
    deleteLink = document.createElement(this.buttonNodeType),
    //Creates content div
    contentNode = document.createElement(this.nodeType),
    //Creates content link
    contentLink = document.createElement(this.linkNodeType);

    // Assigns class names
    noteNode.className = "pinpoint-note";
    timeAndDeleteNode.className = 'pinpoint-time-and-delete';
    contentNode.className = 'pinpoint-content';
    // Gives the link an id.
    timeLink.setAttribute('class', 'pinpoint-timelink');
    contentLink.setAttribute('class', 'pinpoint-contentlink')
    // Sets the link to a specific time within the video
    timeLink.setAttribute('href', this.note.url + "#t=" + this.note.seconds );
    contentLink.setAttribute('href', this.note.url + "#t=" + this.note.seconds)

    // Creates the text for the link
    timeLink.innerHTML = this.note.noteTime;
    contentLink.innerHTML = this.note.content;
    timeAndDeleteNode.appendChild(deleteLink);
    timeAndDeleteNode.appendChild(timeLink);
    contentNode.appendChild(contentLink);
    noteNode.appendChild(contentNode);
    noteNode.appendChild(timeAndDeleteNode);

    // Add delete link to note and fire message to JSONparser
    // to remove note.
    deleteLink.setAttribute('class', 'pinpoint-delete');
    deleteLink.setAttribute('href', '#');
    deleteLink.setAttribute('data-seconds', this.note.seconds);
    deleteLink.innerHTML = "x";
    deleteLink.addEventListener('click', function(){
       chrome.runtime.sendMessage({
            method: "remove note",
            url: this.url,
            seconds: this.note.seconds,
        }, this.refreshFunc);
    }.bind(this));
    return noteNode;
  }
};
<commit_msg>Update pinpoint-notes-presenter.js<commit_after>// NotePresenter class used to draw a note called in appendNotes
PinPoint.NotePresenter = function(note, url, refreshFunc) {
  this.note = note;
  this.url = url;
  this.refreshFunc = refreshFunc;
  this.nodeType = "div";
  this.linkNodeType = "a";
  this.buttonNodeType = "button";
}

PinPoint.NotePresenter.prototype = {
  present: function() {
    // Creates a 'div' node that represents a note
    var noteNode = document.createElement(this.nodeType),
    // Creates a 'div' node that represents the time and delete button in the note - when var is included, errors are thrown for some reason.
    timeAndDeleteNode = document.createElement(this.nodeType),
    // Creates an 'a' node to link to a point in the video - when var is included, errors are thrown for some reason.
    timeLink = document.createElement(this.linkNodeType),
    //Creates delete link
    // deleteNode = document.createElement(this.childNodeType);
    deleteLink = document.createElement(this.buttonNodeType),
    //Creates content div
    contentNode = document.createElement(this.nodeType),
    //Creates content link
    contentLink = document.createElement(this.linkNodeType);

    // Assigns class names
    noteNode.className = "pinpoint-note";
    timeAndDeleteNode.className = 'pinpoint-time-and-delete';
    contentNode.className = 'pinpoint-content';
    // Gives the link an id.
    timeLink.setAttribute('class', 'pinpoint-timelink');
    contentLink.setAttribute('class', 'pinpoint-contentlink')
    // Sets the link to a specific time within the video
    timeLink.setAttribute('href', this.note.url + "#t=" + this.note.seconds );
    contentLink.setAttribute('href', this.note.url + "#t=" + this.note.seconds)

    // Creates the text for the link
    timeLink.innerHTML = this.note.noteTime;
    contentLink.innerHTML = this.note.content;
    timeAndDeleteNode.appendChild(deleteLink);
    timeAndDeleteNode.appendChild(timeLink);
    contentNode.appendChild(contentLink);
    noteNode.appendChild(contentNode);
    noteNode.appendChild(timeAndDeleteNode);

    // Add delete link to note and fire message to popup.js
    // to remove note.
    deleteLink.setAttribute('class', 'pinpoint-delete');
    deleteLink.setAttribute('href', '#');
    deleteLink.setAttribute('data-seconds', this.note.seconds);
    deleteLink.innerHTML = "x";
    deleteLink.addEventListener('click', function(){
       chrome.runtime.sendMessage({
            method: "remove note",
            url: this.url,
            seconds: this.note.seconds,
        }, this.refreshFunc);
    }.bind(this));
    return noteNode;
  }
};
<|endoftext|>


<commit_before>/**
 * This file is part of Wizkers.io
 *
 * The MIT License (MIT)
 *  Copyright (c) 2016 Edouard Lafargue, ed@wizkers.io
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

// This detects whether we are in a server situation and act accordingly:
if (typeof define !== 'function') {
    var define = require('amdefine')(module);
    var vizapp = { type: 'server'},
    events = require('events'),
    debug = require('debug')('wizkers:connections:ble');
    dbs = require('pouch-config');
}

/**
 * Bluetooth Low Energy connection, implemented using the Node NOBLE
 * API ()
 *
 */
define(function (require) {

    "use strict";

    var EventEmitter = require('events').EventEmitter,
        noble = require('noble'),
        util = require('util'),
        abu = require('app/lib/abutils');

    // TODO: accept a list of service UUIDs and a list of Characteristic UUIDs
    //       rather than a single value.

    /**
     * Instanciates a BTLE connection
     * @param {Object} path  Contains the address of the BTLE device we want to connect to.
     * @param {Object} settings not used.
     */
    var nodeBTLE = function (path, settings) {

        EventEmitter.call(this);

        /////////
        // Initialization
        /////////
        var portOpen = false,
            deviceDisappeared = false, // becomes true if the device disappeared (we will try to
            // reconnect if it comes back
            currentService = null,
            currentCharacteristics = null,
            self = this,
            devAddress = path,
            timeoutCheckTimer = 0;

        // We want to keep a reference to the peripheral, services and characteristics
        var activePeripheral = null,
            activeServices = null,       // All found services
            activeCharacteristics = null,
            foundPeripheral = false;

        var subscribedCharacteristics = [];

        ///////////
        // Public methods
        ///////////

        /**
         * Send data
         * @param {ArrayBuffer} data The command, already formatted for sending.
         * @param {Object} info contains the service_uuid and characterictic_uuid to write to
         */
        this.write = function (data, info, callback) {
            if (!portOpen || info == undefined || callback == undefined)
                return;

            var encodedString = bluetoothle.bytesToEncodedString((data instanceof Uint8Array) ? data : new Uint8Array(data));
            var params = {
                value: encodedString,
                address: devAddress,
                service: info.service_uuid,
                characteristic: info.characteristic_uuid,
            };
            if (info.type) {
                params.type = info.type;
            }
            bluetoothle.write(callback,
                              function(e) { console.error('BLE Write error', e)},
                              params);
        };

        /**
         * Read data from the open BTLE device
         */
        this.read = function (service, characteristic) {
            if (!portOpen)
                return;

            var readError = function(e) {
                console.log('[cordovaBTLE] Read error: ' + e);
            };

            var readSuccess = function(s) {
                // Our drivers don't want this base64 stuff, sorry
                s.value = bluetoothle.encodedStringToBytes(s.value)
                    // Pass it on to the driver
                self.emit('data', s);
            };

            bluetoothle.read(readSuccess,readError,{
                'address': devAddress,
                'service': service,
                'characteristic': characteristic
            });

        }

        /**
         * Subscribe to a service/characteristic.
         * You can subscribe to multiple characteristings within a service,
         * but not across services
         * @param {[[Type]]} subscribeInfo [[Description]]
         */
        this.subscribe = function (subscribeInfo) {
            if (!portOpen)
                return;
            // Once we are connected, we want to select the service
            // the driver asked for and subscribe to the characteristics
            // the driver wants.

            // It seems that Noble uses uuids without dashed
            subscribeInfo.service_uuid = subscribeInfo.service_uuid.replace(/-/gi,'');
            debug(subscribeInfo);

            // With the way the Noble API works, we now need to discover
            // characteristics for the service we're looking for before
            // going further
            var s = null;
            for (var i in activeServices) {
                if (activeServices[i].uuid == subscribeInfo.service_uuid) {
                    s = activeServices[i];
                    break;
                }
            }
            if (s == null) {
                this.close();
                return;
            }
            debug('Found my service');
            if (typeof subscribeInfo.characteristic_uuid == 'object') { // Arrays are objects in Node
                var cuid = [];
                for (var i in subscribeInfo.characteristic_uuid ) {
                    cuid.push(subscribeInfo.characteristic_uuid[i].replace(/-/gi,''));
                }
            } else {
                var cuid = [ subscribeInfo.characteristic_uuid.replace(/-/gi,'') ]
            }
            s.discoverCharacteristics(cuid , function(err, c) {
                debug('Found characteristics', c);
                s.removeAllListeners('characteristicsDiscover'); // Workaround on a noble bug (?)
                if (typeof c == 'undefined' ) {
                    debug('Error: could not find a matching characteristic');
                    self.emit('status', {
                        openerror: true,
                        reason: 'Could not connect to the BLE service',
                        description: 'Did not find characteristic.'
                    });
                    self.close();
                    return;
                }

                for (var i in c ) {
                    // If we were already subscribed to this characteristic in the past,
                    // we need to remove the previous listener first, otherwise we'll get
                    // every even in double
                    cleanDataListeners(c[i]);
                    c[i].subscribe(trackError);
                    var makeOnData = function(uuid) {
                        return function(data, isNotification) {
                            // debug('Received data from BLE device', data, isNotification);
                            self.emit('data', { value: data, uuid: uuid });
                        }
                    }
                    c[i].on('data', makeOnData(c[i].uuid));
                    subscribedCharacteristics.push(c[i]);
                }
            });

            return;
        }

        this.close = function () {
            debug("[nodeBTLE] Close BTLE connection");
            if (activePeripheral == null) {
                debug('Error: no active peripheral and asked to close?');
                return;
            }
            // First we unsubscribe from all characteristics we're listening to:
            for (var i in subscribedCharacteristics) {
                subscribedCharacteristics[i].unsubscribe();
                subscribedCharacteristics[i].removeAllListeners('data');
            }
            subscribedCharacteristics = [];
            activePeripheral.disconnect(function (result) {
                debug('Peripheral closed');
                portOpen = false;
                self.emit('status', {
                    portopen: portOpen
                });
            });
        };

        // Has to be called by the backend_driver to actually open the port.
        // This just connects to the device
        this.open = function () {
            if (typeof noble.wizkersScanningCount == 'undefined')
                noble.wizkersScanningCount = 0;
            // noble, the NodeJS we use, seems to require a scan to find
            // the device, then do the actual connect
            foundPeripheral = false;
            noble.wizkersScanningCount++;
            noble.startScanning([], true);

            // Setup a callback in case we time out
            timeoutCheckTimer = setTimeout(checkConnectDelay, 30000);

            noble.on('discover', doConnect);

        }

        /////////////
        // Private methods
        /////////////

        // Callback once we have found a BLE peripheral
        function doConnect(peripheral) {
            if (peripheral.id != devAddress) {
                debug('Peripheral found (' + peripheral.id + ') but not the one we want');
                return;
            }
            if (foundPeripheral)
                return; // In case scan is still going on
            foundPeripheral = true;
            // We might have several peripherals trying to connect at once,
            // so we must not stop scanning before they have all either connected
            // or given up
            noble.wizkersScanningCount--;
            debug('Number of scans going on now:', noble.wizkersScanningCount);
            if (noble.wizkersScanningCount == 0) {
                debug('Stop scanning');
                noble.removeAllListeners('discover');
                noble.stopScanning();
            }
            debug('Found our peripheral, now connecting (' + peripheral.id + ')');
            activePeripheral = peripheral;
            peripheral.connect(trackConnect);
            peripheral.on('disconnect', trackError); // Track disconnects
        }

        /**
         * This implements BLE Autoconnect using a device filter.
         * Connection will be attempted with the first returned device that
         * is discovered and matches the filter.
         */
        var autoConnect = function (filter) {
            debug('Autoconnect not implemented');
        }

        /**
         * Delete any existing listener for data messages from
         * a given characteristic
         * @param {*} c Characteristic to look for
         */
        var cleanDataListeners = function(c) {
            for (var i in subscribedCharacteristics) {
                if (c.uuid == subscribedCharacteristics[i].uuid) {
                    subscribedCharacteristics[i].unsubscribe();
                    subscribedCharacteristics[i].removeAllListeners('data');
                    subscribedCharacteristics.splice(i,1); // Remove old characteristic
                }
            }
        }
        /////////////
        //   Callbacks
        /////////////

        // Track connection status
        function trackConnect(error) {

            debug('We are now connected, discovering services/characteristics');

            // Right after we connect, we do a service
            // discovery, so that we can then connect to the various
            // services & characteristics. This is the Android call, the
            // iPhone call will be different:
            activePeripheral.discoverServices( [], function (err, services) {
                portOpen = true;
                if (timeoutCheckTimer) {
                    clearTimeout(timeoutCheckTimer);
                    timeoutCheckTimer = 0;
                }

                activeServices = services;

                // Create a list of service UUIDs
                var suuids = [];
                for (var service in services) {
                    // Todo: reformat UUID to include dashes ?
                    suuids.push(services[service].uuid);
                }

                self.emit('status', {
                    portopen: portOpen,
                    services: suuids
                });

                // Need to send this to tell the front-end we're done reconnecting
                // and back to normal
                self.emit('status', {
                    reconnecting: false
                });
            });
        }

        function trackError(err) {
            // This is called whenever we lose the connection

            if (!portOpen) {
                // if the port was not open and we got an error callback, this means
                // we were not able to connect in the first place...
                self.emit('status', {
                    openerror: true,
                    reason: 'Device connection error',
                    description: 'Device got disconnected'
                });
                // Do a disconnect to make sure we end up in a sane state:
                self.close();
            } else {
                // portOpen = false;
                // Just keep reconnecting forever...
                debug('Error while we were connected ???', err);
                //self.emit('status', {
                //    reconnecting: true
                //});
            }
            return;
        }

        // Make sure that if after X seconds we have no connection, we cancel
        // the attempt
        function checkConnectDelay() {
            debug('Check connect delay timeout');
            // We might have several peripherals trying to connect at once,
            // so we must not stop scanning before they have all either connected
            // or given up
            noble.wizkersScanningCount--;
            if (noble.wizkersScanningCount == 0) {
                noble.removeAllListeners('discover');
                noble.stopScanning();
            }
            if (!portOpen) {
                self.close();
                self.emit('status', {
                    openerror: true,
                    reason: 'Device connection error',
                    description: 'Could not connect to device. In you just enabled bluetooth, you might have to wait up to 15 seconds before you can connect.'
                });
            }
        }

        debug("[nodeBTLE] Node BTLE Library loaded");

    };

    // Add event management to our serial lib, from the Backbone.Events class:
    util.inherits(nodeBTLE, EventEmitter);

    return nodeBTLE;
});<commit_msg>Node BLE connection driver: proper callback cleanup for multiple open/close<commit_after>/**
 * This file is part of Wizkers.io
 *
 * The MIT License (MIT)
 *  Copyright (c) 2016 Edouard Lafargue, ed@wizkers.io
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

// This detects whether we are in a server situation and act accordingly:
if (typeof define !== 'function') {
    var define = require('amdefine')(module);
    var vizapp = { type: 'server'},
    events = require('events'),
    debug = require('debug')('wizkers:connections:ble');
    dbs = require('pouch-config');
}

/**
 * Bluetooth Low Energy connection, implemented using the Node NOBLE
 * API ()
 *
 */
define(function (require) {

    "use strict";

    var EventEmitter = require('events').EventEmitter,
        noble = require('noble'),
        util = require('util'),
        abu = require('app/lib/abutils');

    // TODO: accept a list of service UUIDs and a list of Characteristic UUIDs
    //       rather than a single value.

    /**
     * Instanciates a BTLE connection
     * @param {Object} path  Contains the address of the BTLE device we want to connect to.
     * @param {Object} settings not used.
     */
    var nodeBTLE = function (path, settings) {

        EventEmitter.call(this);

        /////////
        // Initialization
        /////////
        var portOpen = false,
            deviceDisappeared = false, // becomes true if the device disappeared (we will try to
            // reconnect if it comes back
            currentService = null,
            currentCharacteristics = null,
            self = this,
            devAddress = path,
            timeoutCheckTimer = 0;

        // We want to keep a reference to the peripheral, services and characteristics
        var activePeripheral = null,
            activeServices = null,       // All found services
            activeCharacteristics = null,
            foundPeripheral = false;

        var subscribedCharacteristics = [];

        ///////////
        // Public methods
        ///////////

        /**
         * Send data
         * @param {ArrayBuffer} data The command, already formatted for sending.
         * @param {Object} info contains the service_uuid and characterictic_uuid to write to
         */
        this.write = function (data, info, callback) {
            if (!portOpen || info == undefined || callback == undefined)
                return;

            var encodedString = bluetoothle.bytesToEncodedString((data instanceof Uint8Array) ? data : new Uint8Array(data));
            var params = {
                value: encodedString,
                address: devAddress,
                service: info.service_uuid,
                characteristic: info.characteristic_uuid,
            };
            if (info.type) {
                params.type = info.type;
            }
            bluetoothle.write(callback,
                              function(e) { console.error('BLE Write error', e)},
                              params);
        };

        /**
         * Read data from the open BTLE device
         */
        this.read = function (service, characteristic) {
            if (!portOpen)
                return;

            var readError = function(e) {
                console.log('[cordovaBTLE] Read error: ' + e);
            };

            var readSuccess = function(s) {
                // Our drivers don't want this base64 stuff, sorry
                s.value = bluetoothle.encodedStringToBytes(s.value)
                    // Pass it on to the driver
                self.emit('data', s);
            };

            bluetoothle.read(readSuccess,readError,{
                'address': devAddress,
                'service': service,
                'characteristic': characteristic
            });

        }

        /**
         * Subscribe to a service/characteristic.
         * You can subscribe to multiple characteristings within a service,
         * but not across services
         * @param {[[Type]]} subscribeInfo [[Description]]
         */
        this.subscribe = function (subscribeInfo) {
            if (!portOpen)
                return;
            // Once we are connected, we want to select the service
            // the driver asked for and subscribe to the characteristics
            // the driver wants.

            // It seems that Noble uses uuids without dashed
            subscribeInfo.service_uuid = subscribeInfo.service_uuid.replace(/-/gi,'');
            debug(subscribeInfo);

            // With the way the Noble API works, we now need to discover
            // characteristics for the service we're looking for before
            // going further
            var s = null;
            for (var i in activeServices) {
                if (activeServices[i].uuid == subscribeInfo.service_uuid) {
                    s = activeServices[i];
                    break;
                }
            }
            if (s == null) {
                this.close();
                return;
            }
            debug('Found my service');
            if (typeof subscribeInfo.characteristic_uuid == 'object') { // Arrays are objects in Node
                var cuid = [];
                for (var i in subscribeInfo.characteristic_uuid ) {
                    cuid.push(subscribeInfo.characteristic_uuid[i].replace(/-/gi,''));
                }
            } else {
                var cuid = [ subscribeInfo.characteristic_uuid.replace(/-/gi,'') ]
            }
            s.discoverCharacteristics(cuid , function(err, c) {
                debug('Found characteristics', c);
                s.removeAllListeners('characteristicsDiscover'); // Workaround on a noble bug (?)
                if (typeof c == 'undefined' ) {
                    debug('Error: could not find a matching characteristic');
                    self.emit('status', {
                        openerror: true,
                        reason: 'Could not connect to the BLE service',
                        description: 'Did not find characteristic.'
                    });
                    self.close();
                    return;
                }

                for (var i in c ) {
                    // If we were already subscribed to this characteristic in the past,
                    // we need to remove the previous listener first, otherwise we'll get
                    // every even in double
                    cleanDataListeners(c[i]);
                    c[i].subscribe(trackCharacteristicError);
                    var makeOnData = function(uuid) {
                        return function(data, isNotification) {
                            // debug('Received data from BLE device', data, isNotification);
                            self.emit('data', { value: data, uuid: uuid });
                        }
                    }
                    c[i].on('data', makeOnData(c[i].uuid));
                    subscribedCharacteristics.push(c[i]);
                }
            });

            return;
        }

        this.close = function () {
            debug("[nodeBTLE] Close BTLE connection");
            if (activePeripheral == null) {
                debug('Error: no active peripheral and asked to close?');
                return;
            }
            // First we unsubscribe from all characteristics we're listening to:
            for (var i in subscribedCharacteristics) {
                subscribedCharacteristics[i].unsubscribe();
                subscribedCharacteristics[i].removeAllListeners('data');
            }
            subscribedCharacteristics = [];
            activePeripheral.removeAllListeners('connect'); // Just in case
            activePeripheral.disconnect(function (result) {
                debug('Peripheral closed');
                portOpen = false;
                self.emit('status', {
                    portopen: portOpen
                });
            });
        };

        // Has to be called by the backend_driver to actually open the port.
        // This just connects to the device
        this.open = function () {
            if (typeof noble.wizkersScanningCount == 'undefined')
                noble.wizkersScanningCount = 0;
            // noble, the NodeJS we use, seems to require a scan to find
            // the device, then do the actual connect
            foundPeripheral = false;
            noble.wizkersScanningCount++;
            noble.startScanning([], true);

            // Setup a callback in case we time out
            timeoutCheckTimer = setTimeout(checkConnectDelay, 30000);

            noble.on('discover', doConnect);

        }

        /////////////
        // Private methods
        /////////////

        // Callback once we have found a BLE peripheral
        function doConnect(peripheral) {
            if (peripheral.id != devAddress) {
                debug('Peripheral found (' + peripheral.id + ') but not the one we want');
                return;
            }
            if (foundPeripheral)
                return; // In case scan is still going on
            foundPeripheral = true;
            // We might have several peripherals trying to connect at once,
            // so we must not stop scanning before they have all either connected
            // or given up
            noble.wizkersScanningCount--;
            debug('Number of scans going on now:', noble.wizkersScanningCount);
            if (noble.wizkersScanningCount == 0) {
                debug('Stop scanning');
                noble.removeAllListeners('discover');
                noble.stopScanning();
            }
            debug('Found our peripheral, now connecting (' + peripheral.id + ')');
            activePeripheral = peripheral;
            peripheral.connect(trackConnect);
            peripheral.on('disconnect', trackError); // Track disconnects
        }

        /**
         * This implements BLE Autoconnect using a device filter.
         * Connection will be attempted with the first returned device that
         * is discovered and matches the filter.
         */
        var autoConnect = function (filter) {
            debug('Autoconnect not implemented');
        }

        /**
         * Delete any existing listener for data messages from
         * a given characteristic
         * @param {*} c Characteristic to look for
         */
        var cleanDataListeners = function(c) {
            for (var i in subscribedCharacteristics) {
                if (c.uuid == subscribedCharacteristics[i].uuid) {
                    subscribedCharacteristics[i].unsubscribe();
                    subscribedCharacteristics[i].removeAllListeners('data');
                    subscribedCharacteristics.splice(i,1); // Remove old characteristic
                }
            }
        }
        /////////////
        //   Callbacks
        /////////////

        // Track connection status
        function trackConnect(error) {

            debug('We are now connected, discovering services/characteristics');

            // Right after we connect, we do a service
            // discovery, so that we can then connect to the various
            // services & characteristics. This is the Android call, the
            // iPhone call will be different:
            activePeripheral.discoverServices( [], function (err, services) {
                portOpen = true;
                if (timeoutCheckTimer) {
                    clearTimeout(timeoutCheckTimer);
                    timeoutCheckTimer = 0;
                }

                activeServices = services;

                // Create a list of service UUIDs
                var suuids = [];
                for (var service in services) {
                    // Todo: reformat UUID to include dashes ?
                    suuids.push(services[service].uuid);
                }

                self.emit('status', {
                    portopen: portOpen,
                    services: suuids
                });

                // Need to send this to tell the front-end we're done reconnecting
                // and back to normal
                self.emit('status', {
                    reconnecting: false
                });
            });
        }

        function trackCharacteristicError(err) {
            debug('Characteristic subscribe result:', err);
        }

        function trackError(err) {
            activePeripheral.removeAllListeners('disconnect');
            // This is called whenever we lose the connection
            if (!portOpen) {
                // if the port was not open and we got an error callback, this means
                // we were not able to connect in the first place...
                self.emit('status', {
                    openerror: true,
                    reason: 'Device connection error',
                    description: 'Device got disconnected'
                });
                // Do a disconnect to make sure we end up in a sane state:
                self.close();
            } else {
                // portOpen = false;
                // Just keep reconnecting forever...
                debug('Error while we were connected ???', err);
                //self.emit('status', {
                //    reconnecting: true
                //});
            }
            return;
        }

        // Make sure that if after X seconds we have no connection, we cancel
        // the attempt
        function checkConnectDelay() {
            debug('Check connect delay timeout');
            // We might have several peripherals trying to connect at once,
            // so we must not stop scanning before they have all either connected
            // or given up
            noble.wizkersScanningCount--;
            if (noble.wizkersScanningCount == 0) {
                noble.removeAllListeners('discover');
                noble.stopScanning();
            }
            if (!portOpen) {
                self.close();
                self.emit('status', {
                    openerror: true,
                    reason: 'Device connection error',
                    description: 'Could not connect to device. In you just enabled bluetooth, you might have to wait up to 15 seconds before you can connect.'
                });
            }
        }

        debug("[nodeBTLE] Node BTLE Library loaded");

    };

    // Add event management to our serial lib, from the Backbone.Events class:
    util.inherits(nodeBTLE, EventEmitter);

    return nodeBTLE;
});<|endoftext|>