<commit_before>/*jshint scripturl:true, smarttabs:true */

/**
 * Copyright 2013 Sean Coker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
 /*!
 * @author Sean Coker <sean@seancoker.com>
 * @url http://sean.is/poppin/tags
 * @description Taggle is a simple delicious style tagging plugin
 */

;(function(window, document, undefined) {

    var defaults = {

        /**
         * Class names to be added on each tag entered
         * @type {String}
         */
        additionalTagClasses:   '',

        /**
         * Allow duplicate tags to be entered in the field?
         * @type {Boolean}
         */
        allowDuplicates:        false,

        /**
         * Class name that will be added onto duplicate existant tag
         * @type {String}
         */
        duplicateTagClass:      '',

        /**
         * Class added to the container div when focused
         * @type {String}
         */
        containerFocusClass:    'active',

        /**
         * Name added to the hidden inputs within each tag
         * @type {String}
         */
        hiddenInputName:        'taggles[]',

        /**
         * Tags that should be preloaded in the div on load
         * @type {Array}
         */
        tags:                   [],

        /**
         * If within a form, you can specify the tab index flow
         * @type {Number}
         */
        tabIndex:               1,

        /**
         * Placeholder string to be placed in an empty taggle field
         * @type {String}
         */
        placeholder:            'Enter tags...'
    },

    BACKSPACE = 8,
    COMMA = 188,
    TAB = 9,
    ENTER = 13;

    /**
     * Constructor
     * @param {Mixed} el ID of an element or the actual element
     * @param {Object} options
     */
    var Taggle = function(el, options) {
        var self = this;

        self.container = el;
        self.options = _extend({}, defaults, options);
        self.tag = {
            values: [],
            elements: []
        };
        self.measurements = {
            container: {
                rect: null,
                style: null,
                side_padding: null
            }
        };

        self.list = document.createElement('ul');
        self.input_li = document.createElement('li');
        self.input = document.createElement('input');

        if (self.options.placeholder) {
            self.placeholder = document.createElement('span');
        }

        if (typeof el === 'string') {
            self.container = document.getElementById(el);
        }

        // Bang bang bang skeet skeet
        self.getMeasurements();
        self.setupTextarea();
        self.attachEvents();
    };

    /**
     * Gets all the layout measurements up front
     */
    Taggle.prototype.getMeasurements = function() {
        var self = this,
            style;

        self.measurements.container.rect = self.container.getBoundingClientRect();
        self.measurements.container.style = window.getComputedStyle(self.container);

        style = self.measurements.container.style;
        self.measurements.container.side_padding = parseInt(style['padding-left'], 10) + parseInt(style['padding-right'], 10);
    };

    /**
     * Setup the div container for tags to be entered
     */
    Taggle.prototype.setupTextarea = function() {
        var self = this;
        self.list.className = 'taggle_list';
        self.tag_input.type = 'text';
        self.tag_input.className = 'taggle_input';
        self.tag_input.tabIndex = self.options.tabIndex;
        self.placeholder.style.display = 'none';
        self.placeholder.classList.add('taggle_placeholder');
        self.container.appendChild(self.placeholder);

        if (self.options.tags !== null) {
            for (var i = 0, len = self.options.tags.length; i < len; i++) {
                var tag = self.createTag(self.options.tags[i]);
                self.list.appendChild(tag);
            }
        }

        if (!!self.options.placeholder) {
            self.placeholder.textContent = self.options.placeholder;
            if (!self.options.tags.length) {
                self.placeholder.style.opacity = 1;
            }
        }

        self.tag_input_li.appendChild(self.tag_input);
        self.list.appendChild(self.tag_input_li);
        self.container.appendChild(self.list);
    };

    /**
     * Attaches events, duh
     */
    Taggle.prototype.attachEvents = function() {
        var self = this,
            closes;

        on(self.container, 'click', function() {
            self.tag_input.focus();
        });

        self.tag_input.onfocus = bind(self, self.inputFocused);
        self.tag_input.onblur = bind(self, self.inputBlurred);

        on(self.tag_input, 'keydown', bind(self, self.keydownEvents));
        on(self.tag_input, 'keyup', bind(self, self.keyupEvents));

        closes = self.container.querySelectorAll('.close');

        for (var i = 0, len = closes.length; i < len; i++) {
            one(closes[i], 'click', bind(self, self.removeTag, [closes[i]]));
        }
    };

    /**
     * Resizes the hidden input where user types to fill in the
     * width of the div
     */
    Taggle.prototype.fixInputWidth = function() {
        var self = this,
            width,
            input_rect,
            left_pos,
            padding;
        //reset width incase we've broken to the next line on a backspace erase
        self.setInputWidth();

        input_rect = self.input.getBoundingClientRect();
        width = ~~self.measurements.container.rect.width;
        left_pos = ~~input_rect.left - ~~self.measurements.container.rect.left;
        padding = self.measurements.container.side_padding;

        self.setInputWidth(width - left_pos - padding);
    };

    /**
     * Appends tag with its corresponding input to the list
     * @param  {String} tag
     */
    Taggle.prototype.add = function(text) {
        var self = this,
            val = typeof text === 'string' ? text.toLowerCase() :
                _trim(self.input.value.toLowerCase()),
            li, last_li;

        if ((!self.options.allowDuplicates && self.hasDupes()) || val === '') {
            return self;
        }

        li = self.createTag(val);

        last_li = self.list.querySelector('li:last-child');
        self.list.insertBefore(li, last_li);

        self.input.value = '';
        self.setInputWidth();
        self.fixInputWidth();
        self.focusInput();

        return self;
    };

    /**
     * Removes last tag if it has already been probed
     * @param  {Event}  e
     */
    Taggle.prototype.chargeLastTag = function(e) {
        e = e || window.event;

        var self = this,
            taggles = self.container.querySelectorAll('.taggle'),
            last_taggle = taggles[taggles.length - 1],
            hot_class = 'hot';

        //8 - backspace
        if (self.tag_input.value === '' && e.keyCode === 8) {
        if (self.input.value === '' && e.keyCode === BACKSPACE) {
            if (last_taggle.classList.contains(hot_class)) {
                self.removeFromTheTags(last_taggle);
                last_taggle.remove();
                self.fixInputWidth();
            }
            else {
                last_taggle.classList.add(hot_class);
            }
        }
        else if (last_taggle.classList.contains(hot_class)) {
            last_taggle.classList.remove(hot_class);
        }
    };

    /**
     * Grabs the text from the li item and removes it from global array
     * @param  {Element} el
     */
    Taggle.prototype.removeFromTheTags = function(el) {
        var self = this,
            elem = (el.tagName.toLowerCase() === 'a') ? el.parentNode : el,
            text = elem.textContent || elem.innerText;

        text = text.slice(0, -1);

        self.tags.splice(self.tags.indexOf(text), 1);
    };

    /**
     * Setter for the hidden input.
     * @param {Number} width
     */
    Taggle.prototype.setInputWidth = function(width) {
        this.tag_input.style.width = (width || 10) + 'px';
    };

    /**
     * Checks global tags array if provided tag exists
     * @param  {String} tag
     */
    Taggle.prototype.checkForDupes = function(tag) {
        var self = this,
            val = self.tag_input.value,
            tag_input_value = tag ? tag.toLowerCase() : trim(val.toLowerCase()),
            needle = self.tags.indexOf(tag_input_value),
            taggle_list = self.container.querySelector('.taggle_list'),
            dupes = taggle_list.querySelectorAll('.' + self.options.duplicateTagClass);

        if (!!self.options.duplicateTagClass && dupes.length) {
            for (var i = 0, len = dupes.length; i < len; i++) {
                dupes[i].classList.remove(self.options.duplicateTagClass);
            }
        }

        //if found
        if (needle > -1) {
            if (self.options.duplicateTagClass) {
                taggle_list.childNodes[needle].classList.add(self.options.duplicateTagClass);
            }
            return true;
        }

        return false;
    };

    /**
     * Checks whether or not the key pressed is acceptable
     * @param  {Event}  e
     * @return {Boolean}
     */
    Taggle.prototype.isConfirmKey = function(e) {
        var code = e.keyCode,
            confirm_key = false;

        if (code === COMMA || code === TAB || code === ENTER) {
            confirm_key = true;
        }

        return confirm_key;
    };

    //event handlers

    /**
     * Handles focus state of div container.
     */
    Taggle.prototype.inputFocused = function() {
        var self = this;

        self.fixInputWidth();

        if (!self.container.classList.contains(self.options.containerFocusClass)) {
            self.container.classList.add(self.options.containerFocusClass);
        }

        if (!!self.options.placeholder) {
            self.placeholder.style.opacity = 0;
        }
    };

    /**
     * Sets state of container when blurred
     */
    Taggle.prototype.inputBlurred = function() {
        var self = this;

        self.tag_input.value = '';
        self.setInputWidth();

        if (self.container.classList.contains(self.options.containerFocusClass)) {
            self.container.classList.remove(self.options.containerFocusClass);
        }

        if (!self.tags.length && self.options.placeholder) {
            self.placeholder.style.opacity = 1;
        }
    };

    /**
     * Runs all the events that need to run on keydown
     * @param  {Event} e
     */
    Taggle.prototype.keydownEvents = function(e) {
        e = e || window.event;

        var self = this;

        self.listenForEndOfContainer();

        if (self.isConfirmKey(e) && self.tag_input.value !== '') {
            self.listenForTagConfirm(e);
        }

        self.chargeLastTag(e);
    };

    /**
     * Runs all the events that need to run on keyup
     * @param  {Event} e
     */
    Taggle.prototype.keyupEvents = function(e) {
        e = e || window.event;
    };

    /**
     * Confirms the inputted value to be converted to a tag
     * @param  {Event} e
     * @return {Boolean}
     */
    Taggle.prototype.listenForTagConfirm = function(e) {
        e = e || window.event;

        var self = this,
            code = e.keyCode,
            dupes;

        //tab, enter
        if (code === TAB || code === ENTER) {
            self.add();
        }

        if (e.preventDefault) {
            e.preventDefault();
        }
        else {
            e.returnValue = false;
        }
    };

    /**
     * Approximates when the hidden input should break to the next line
     */
    Taggle.prototype.listenForEndOfContainer = function() {
        var self = this,
            width = self.sizer.getBoundingClientRect().width,
            max = self.measurements.container.rect.width - self.measurements.container.side_padding,
            size = parseInt(self.sizer.style.fontSize, 10);

        //1.5 just seems to be a good multiplier here
        if (width + (size * 1.5) > parseInt(self.input.style.width, 10)) {
            self.input.style.width = max + 'px';
        }
    };

    Taggle.prototype.createTag = function(text) {
        var self = this;
        var li = document.createElement('li');
        var close = document.createElement('a');
        var hidden = document.createElement('input');
        var span = document.createElement('span');

        text = text.toLowerCase();

        close.href = 'javascript:void(0)';
        close.innerHTML = '×';
        close.className = 'close';
        close.onclick = _bind(self, self.remove, [close]);

        span.textContent = text;
        span.className = 'taggle_text';

        li.className = 'taggle ' + self.options.additionalTagClasses;

        hidden.type = 'hidden';
        hidden.value = text;
        hidden.name = self.options.hiddenInputName;

        li.appendChild(span);
        li.appendChild(close);
        li.appendChild(hidden);

        self.tag.values.push(text);
        self.tag.elements.push(li);

        return li;
    };

    /**
     * Removes tag from the list and global tags array
     * @param  {Event} e
     */
    Taggle.prototype.removeTag = function(e) {
        e = e || window.event;
        var self = this,
            targ = e.target || e,
            li = targ.parentNode;

        li.parentNode.removeChild(li);
        self.inputFocused();
        self.removeFromTheTags(targ);
    };

    function _extend() {
        if (arguments.length < 2) {
            return;
        }
        var master = arguments[0];
        for (var i = 1, l = arguments.length; i < l; i++) {
            var object = arguments[i];
            for (var key in object) {
                if (object.hasOwnProperty(key)) {
                    master[key] = object[key];
                }
            }
        }

        return master;
    }

    /**
     * Grabs the text from the li item and removes it from global array
     * @param  {Element} el
     */
    function _removeFromTheTags(el, tag) {
        var elem = (el.tagName.toLowerCase() === 'a') ? el.parentNode : el,
            index = tag.elements.indexOf(elem);

        // Going to assume the indicies match for now
        tag.elements.splice(index, 1);
        tag.values.splice(index, 1);
    }

    function _on(element, eventName, handler) {
        if (element.addEventListener) {
            element.addEventListener(eventName, handler, false);
        }
        else if (element.attachEvent) {
            element.attachEvent('on' + eventName, handler);
        }
        else {
            element['on' + eventName] = handler;
        }
    }

    function _trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
    }

    function _bind(object, method, args) {
        return function() {
            return method.apply(object, args);
        };
    }

    window.Taggle = Taggle;

}(window, document));
<commit_msg>Fix up events<commit_after>/*jshint scripturl:true, smarttabs:true */

/**
 * Copyright 2013 Sean Coker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
 /*!
 * @author Sean Coker <sean@seancoker.com>
 * @url http://sean.is/poppin/tags
 * @description Taggle is a simple delicious style tagging plugin
 */

;(function(window, document, undefined) {

    var defaults = {

        /**
         * Class names to be added on each tag entered
         * @type {String}
         */
        additionalTagClasses:   '',

        /**
         * Allow duplicate tags to be entered in the field?
         * @type {Boolean}
         */
        allowDuplicates:        false,

        /**
         * Class name that will be added onto duplicate existant tag
         * @type {String}
         */
        duplicateTagClass:      '',

        /**
         * Class added to the container div when focused
         * @type {String}
         */
        containerFocusClass:    'active',

        /**
         * Name added to the hidden inputs within each tag
         * @type {String}
         */
        hiddenInputName:        'taggles[]',

        /**
         * Tags that should be preloaded in the div on load
         * @type {Array}
         */
        tags:                   [],

        /**
         * If within a form, you can specify the tab index flow
         * @type {Number}
         */
        tabIndex:               1,

        /**
         * Placeholder string to be placed in an empty taggle field
         * @type {String}
         */
        placeholder:            'Enter tags...'
    },

    BACKSPACE = 8,
    COMMA = 188,
    TAB = 9,
    ENTER = 13;

    /**
     * Constructor
     * @param {Mixed} el ID of an element or the actual element
     * @param {Object} options
     */
    var Taggle = function(el, options) {
        var self = this;

        self.container = el;
        self.options = _extend({}, defaults, options);
        self.tag = {
            values: [],
            elements: []
        };
        self.measurements = {
            container: {
                rect: null,
                style: null,
                side_padding: null
            }
        };

        self.list = document.createElement('ul');
        self.input_li = document.createElement('li');
        self.input = document.createElement('input');

        if (self.options.placeholder) {
            self.placeholder = document.createElement('span');
        }

        if (typeof el === 'string') {
            self.container = document.getElementById(el);
        }

        // Bang bang bang skeet skeet
        self.getMeasurements();
        self.setupTextarea();
        self.attachEvents();
    };

    /**
     * Gets all the layout measurements up front
     */
    Taggle.prototype.getMeasurements = function() {
        var self = this,
            style;

        self.measurements.container.rect = self.container.getBoundingClientRect();
        self.measurements.container.style = window.getComputedStyle(self.container);

        style = self.measurements.container.style;
        self.measurements.container.side_padding = parseInt(style['padding-left'], 10) + parseInt(style['padding-right'], 10);
    };

    /**
     * Setup the div container for tags to be entered
     */
    Taggle.prototype.setupTextarea = function() {
        var self = this;
        self.list.className = 'taggle_list';
        self.tag_input.type = 'text';
        self.tag_input.className = 'taggle_input';
        self.tag_input.tabIndex = self.options.tabIndex;
        self.placeholder.style.display = 'none';
        self.placeholder.classList.add('taggle_placeholder');
        self.container.appendChild(self.placeholder);

        if (self.options.tags !== null) {
            for (var i = 0, len = self.options.tags.length; i < len; i++) {
                var tag = self.createTag(self.options.tags[i]);
                self.list.appendChild(tag);
            }
        }

        if (!!self.options.placeholder) {
            self.placeholder.textContent = self.options.placeholder;
            if (!self.options.tags.length) {
                self.placeholder.style.opacity = 1;
            }
        }

        self.tag_input_li.appendChild(self.tag_input);
        self.list.appendChild(self.tag_input_li);
        self.container.appendChild(self.list);
    };

    /**
     * Attaches neccessary events
     */
    Taggle.prototype.attachEvents = function() {
        var self = this;

        _on(self.container, 'click', function() {
            self.input.focus();
        });

        self.input.onfocus = _bind(self, self.focusInput);
        self.input.onblur = _bind(self, self.blurInput);

        _on(self.input, 'keydown', _bind(self, self.keydownEvents));
        _on(self.input, 'keyup', _bind(self, self.keyupEvents));
    };

    /**
     * Resizes the hidden input where user types to fill in the
     * width of the div
     */
    Taggle.prototype.fixInputWidth = function() {
        var self = this,
            width,
            input_rect,
            left_pos,
            padding;
        //reset width incase we've broken to the next line on a backspace erase
        self.setInputWidth();

        input_rect = self.input.getBoundingClientRect();
        width = ~~self.measurements.container.rect.width;
        left_pos = ~~input_rect.left - ~~self.measurements.container.rect.left;
        padding = self.measurements.container.side_padding;

        self.setInputWidth(width - left_pos - padding);
    };

    /**
     * Appends tag with its corresponding input to the list
     * @param  {String} tag
     */
    Taggle.prototype.add = function(text) {
        var self = this,
            val = typeof text === 'string' ? text.toLowerCase() :
                _trim(self.input.value.toLowerCase()),
            li, last_li;

        if ((!self.options.allowDuplicates && self.hasDupes()) || val === '') {
            return self;
        }

        li = self.createTag(val);

        last_li = self.list.querySelector('li:last-child');
        self.list.insertBefore(li, last_li);

        self.input.value = '';
        self.setInputWidth();
        self.fixInputWidth();
        self.focusInput();

        return self;
    };

    /**
     * Removes last tag if it has already been probed
     * @param  {Event}  e
     */
    Taggle.prototype.chargeLastTag = function(e) {
        e = e || window.event;

        var self = this,
            taggles = self.container.querySelectorAll('.taggle'),
            last_taggle = taggles[taggles.length - 1],
            hot_class = 'hot';

        //8 - backspace
        if (self.tag_input.value === '' && e.keyCode === 8) {
        if (self.input.value === '' && e.keyCode === BACKSPACE) {
            if (last_taggle.classList.contains(hot_class)) {
                self.removeFromTheTags(last_taggle);
                last_taggle.remove();
                self.fixInputWidth();
            }
            else {
                last_taggle.classList.add(hot_class);
            }
        }
        else if (last_taggle.classList.contains(hot_class)) {
            last_taggle.classList.remove(hot_class);
        }
    };

    /**
     * Setter for the hidden input.
     * @param {Number} width
     */
    Taggle.prototype.setInputWidth = function(width) {
        this.tag_input.style.width = (width || 10) + 'px';
    };

    /**
     * Checks global tags array if provided tag exists
     * @param  {String} tag
     */
    Taggle.prototype.checkForDupes = function(tag) {
        var self = this,
            val = self.tag_input.value,
            tag_input_value = tag ? tag.toLowerCase() : trim(val.toLowerCase()),
            needle = self.tags.indexOf(tag_input_value),
            taggle_list = self.container.querySelector('.taggle_list'),
            dupes = taggle_list.querySelectorAll('.' + self.options.duplicateTagClass);

        if (!!self.options.duplicateTagClass && dupes.length) {
            for (var i = 0, len = dupes.length; i < len; i++) {
                dupes[i].classList.remove(self.options.duplicateTagClass);
            }
        }

        //if found
        if (needle > -1) {
            if (self.options.duplicateTagClass) {
                taggle_list.childNodes[needle].classList.add(self.options.duplicateTagClass);
            }
            return true;
        }

        return false;
    };

    /**
     * Checks whether or not the key pressed is acceptable
     * @param  {Event}  e
     * @return {Boolean}
     */
    Taggle.prototype.isConfirmKey = function(e) {
        var code = e.keyCode,
            confirm_key = false;

        if (code === COMMA || code === TAB || code === ENTER) {
            confirm_key = true;
        }

        return confirm_key;
    };

    //event handlers

    /**
     * Handles focus state of div container.
     */
    Taggle.prototype.inputFocused = function() {
        var self = this;

        self.fixInputWidth();

        if (!self.container.classList.contains(self.options.containerFocusClass)) {
            self.container.classList.add(self.options.containerFocusClass);
        }

        if (!!self.options.placeholder) {
            self.placeholder.style.opacity = 0;
        }
    };

    /**
     * Sets state of container when blurred
     */
    Taggle.prototype.inputBlurred = function() {
        var self = this;

        self.tag_input.value = '';
        self.setInputWidth();

        if (self.container.classList.contains(self.options.containerFocusClass)) {
            self.container.classList.remove(self.options.containerFocusClass);
        }

        if (!self.tags.length && self.options.placeholder) {
            self.placeholder.style.opacity = 1;
        }
    };

    /**
     * Runs all the events that need to run on keydown
     * @param  {Event} e
     */
    Taggle.prototype.keydownEvents = function(e) {
        e = e || window.event;

        var self = this;

        self.listenForEndOfContainer();

        if (self.isConfirmKey(e) && self.tag_input.value !== '') {
            self.listenForTagConfirm(e);
        }

        self.chargeLastTag(e);
    };

    /**
     * Runs all the events that need to run on keyup
     * @param  {Event} e
     */
    Taggle.prototype.keyupEvents = function(e) {
        e = e || window.event;
    };

    /**
     * Confirms the inputted value to be converted to a tag
     * @param  {Event} e
     * @return {Boolean}
     */
    Taggle.prototype.listenForTagConfirm = function(e) {
        e = e || window.event;

        var self = this,
            code = e.keyCode;

        //tab, enter
        if (code === TAB || code === ENTER) {
            self.add();
        }

        if (e.preventDefault) {
            e.preventDefault();
        }
        else {
            e.returnValue = false;
        }
    };

    /**
     * Approximates when the hidden input should break to the next line
     */
    Taggle.prototype.listenForEndOfContainer = function() {
        var self = this,
            width = self.sizer.getBoundingClientRect().width,
            max = self.measurements.container.rect.width - self.measurements.container.side_padding,
            size = parseInt(self.sizer.style.fontSize, 10);

        //1.5 just seems to be a good multiplier here
        if (width + (size * 1.5) > parseInt(self.input.style.width, 10)) {
            self.input.style.width = max + 'px';
        }
    };

    Taggle.prototype.createTag = function(text) {
        var self = this;
        var li = document.createElement('li');
        var close = document.createElement('a');
        var hidden = document.createElement('input');
        var span = document.createElement('span');

        text = text.toLowerCase();

        close.href = 'javascript:void(0)';
        close.innerHTML = '×';
        close.className = 'close';
        close.onclick = _bind(self, self.remove, [close]);

        span.textContent = text;
        span.className = 'taggle_text';

        li.className = 'taggle ' + self.options.additionalTagClasses;

        hidden.type = 'hidden';
        hidden.value = text;
        hidden.name = self.options.hiddenInputName;

        li.appendChild(span);
        li.appendChild(close);
        li.appendChild(hidden);

        self.tag.values.push(text);
        self.tag.elements.push(li);

        return li;
    };

    /**
     * Removes tag from the tags collection
     * @param  {Event} e
     */
    Taggle.prototype.remove = function(e) {
        e = e || window.event;
        var self = this,
            targ = e.target || e.srcElement || e,
            li = targ;

        if (li.tagName.toLowerCase() !== 'li') {
            li = li.parentNode;
        }

        li.parentNode.removeChild(li);
        _removeFromTheTags(li, self.tag);

        self.focusInput();
    };
    };

    function _extend() {
        if (arguments.length < 2) {
            return;
        }
        var master = arguments[0];
        for (var i = 1, l = arguments.length; i < l; i++) {
            var object = arguments[i];
            for (var key in object) {
                if (object.hasOwnProperty(key)) {
                    master[key] = object[key];
                }
            }
        }

        return master;
    }

    /**
     * Grabs the text from the li item and removes it from global array
     * @param  {Element} el
     */
    function _removeFromTheTags(el, tag) {
        var elem = (el.tagName.toLowerCase() === 'a') ? el.parentNode : el,
            index = tag.elements.indexOf(elem);

        // Going to assume the indicies match for now
        tag.elements.splice(index, 1);
        tag.values.splice(index, 1);
    }

    function _on(element, eventName, handler) {
        if (element.addEventListener) {
            element.addEventListener(eventName, handler, false);
        }
        else if (element.attachEvent) {
            element.attachEvent('on' + eventName, handler);
        }
        else {
            element['on' + eventName] = handler;
        }
    }

    function _trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
    }

    function _bind(object, method, args) {
        return function() {
            return method.apply(object, args);
        };
    }

    window.Taggle = Taggle;

}(window, document));
<|endoftext|>


<commit_before>// in test/e2e/main.spec.js
describe('E2E: main page', function () {

	var ptor;
	browser.get('http://127.0.0.1:9000/');

	// body...
	beforeEach(function () {
		ptor = protractor.getInstance();
	});

	it('should open file dialog', function () {
		var path = require('path');
		var fileToUpload1 = '../../app/testData/oldFormat/msajc003/msajc003.wav';
		var fileToUpload2 = '../../app/testData/oldFormat/msajc003/msajc003.TextGrid';
		var absolutePath1 = path.resolve(__dirname, fileToUpload1);
		var absolutePath2 = path.resolve(__dirname, fileToUpload2);
		element(by.id('fileDialog')).sendKeys(absolutePath2);
		element(by.id('fileDialog')).sendKeys(absolutePath1);
		ptor.sleep(200);
		var elem = element.all(by.css('.emuwebapp-levelOuter'));
		expect(elem.count()).toBe(11);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should clear view and open demo1', function() {
		element(by.id('demoDB')).click();
		element(by.id('demo1')).click();
		var elem = element.all(by.css('.emuwebapp-levelMarkupCanvas'))
		expect(elem.count()).toBe(3);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should clear view and open demo2', function() {
		element(by.id('demoDB')).click();
		element(by.id('demo2')).click();
		var elem = element.all(by.css('.emuwebapp-levelMarkupCanvas'))
		expect(elem.count()).toBe(1);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should open first demo DB with 2 levels', function () {
		element(by.id('demoDB')).click();
		element(by.id('demo0')).click();
		var elems = element.all(by.repeater('level in dataServ.data.levels | levelsFilter'));
		expect(elems.count()).toBe(2);
	});
});<commit_msg>Fixes level filter according to view<commit_after>// in test/e2e/main.spec.js
describe('E2E: main page', function () {

	var ptor;
	browser.get('http://127.0.0.1:9000/');

	// body...
	beforeEach(function () {
		ptor = protractor.getInstance();
	});

	it('should open file dialog', function () {
		var path = require('path');
		var fileToUpload1 = '../../app/testData/oldFormat/msajc003/msajc003.wav';
		var fileToUpload2 = '../../app/testData/oldFormat/msajc003/msajc003.TextGrid';
		var absolutePath1 = path.resolve(__dirname, fileToUpload1);
		var absolutePath2 = path.resolve(__dirname, fileToUpload2);
		element(by.id('fileDialog')).sendKeys(absolutePath2);
		element(by.id('fileDialog')).sendKeys(absolutePath1);
		ptor.sleep(200);
		var elem = element.all(by.css('.emuwebapp-levelOuter'));
		expect(elem.count()).toBe(11);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should clear view and open demo1', function() {
		element(by.id('demoDB')).click();
		element(by.id('demo1')).click();
		var elem = element.all(by.css('.emuwebapp-levelMarkupCanvas'))
		expect(elem.count()).toBe(3);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should clear view and open demo2', function() {
		element(by.id('demoDB')).click();
		element(by.id('demo2')).click();
		var elem = element.all(by.css('.emuwebapp-levelMarkupCanvas'))
		expect(elem.count()).toBe(1);
		element(by.id('clear')).click();
		element(by.id('emuwebapp-modal-confirm')).click();
	});

	it('should open first demo DB with 2 levels', function () {
		element(by.id('demoDB')).click();
		element(by.id('demo0')).click();
		var elems = element.all(by.repeater('level in dataServ.getLevelData() | levelsFilter track by $index'));
		expect(elems.count()).toBe(2);
	});
});<|endoftext|>


<commit_before>var caronte = require('../lib/caronte'),
    expect  = require('expect.js'),
    http    = require('http');

describe('lib/caronte.js', function() {
  describe('#createProxyServer', function() {
    it('should throw without options', function() {
      var error;
      try {
        caronte.createProxyServer();
      } catch(e) {
        error = e;
      }

      expect(error).to.be.an(Error);
    })

    it('should return an object otherwise', function() {
      var obj = caronte.createProxyServer({
        target: 'http://www.google.com:80'
      });

      expect(obj.web).to.be.a(Function);
      expect(obj.ws).to.be.a(Function);
      expect(obj.listen).to.be.a(Function);
    });
  });

  describe('#createProxyServer with forward options and using web-incoming passes', function () {
    it('should pipe the request using web-incoming#stream method', function (done) {
      var proxy = caronte.createProxyServer({
        forward: 'http://127.0.0.1:8080'
      }).listen('8081')

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        source.close();
        proxy.close();
        done();
      });

      source.listen('8080');

      http.request('http://127.0.0.1:8081', function() {}).end();
    })
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should make the request on pipe and finish it', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }).listen('8081');

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('POST');
        expect(req.headers['x-forwarded-for']).to.eql('127.0.0.1');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        source.close();
        proxy.close();
        done();
      });

      source.listen('8080');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'POST',
        headers: {
          'x-forwarded-for': '127.0.0.1'
        }
      }, function() {}).end();
    });
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should make the request, handle response and finish it', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }).listen('8081');

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        res.writeHead(200, {'Content-Type': 'text/plain'})
        res.end('Hello from ' + source.address().port);
      });

      source.listen('8080');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function(res) {
        expect(res.statusCode).to.eql(200);

        res.on('data', function (data) {
          expect(data.toString()).to.eql('Hello from 8080');
        });

        res.on('end', function () {
          source.close();
          proxy.close();
          done();
        });
      }).end();
    });
  });

  describe('#createProxyServer() method with error response', function () {
    it('should make the request and emit the error event', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      });

      proxy.ee.on('caronte:outgoing:web:error', function (err) {
        expect(err).to.be.an(Error);
        expect(err.code).to.be('ECONNREFUSED');
        proxyServer.close();
        done();
      })

      var proxyServer = proxy.listen('8081');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function() {}).end();
    });
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should emit events correclty', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }),

      proxyServer = proxy.listen('8081'),

      source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        res.writeHead(200, {'Content-Type': 'text/plain'})
        res.end('Hello from ' + source.address().port);
      }),

      events = [];

      source.listen('8080');

      proxy.ee.on('caronte:**', function (uno, dos, tres) {
        events.push(this.event);
      })

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function(res) {
        expect(res.statusCode).to.eql(200);

        res.on('data', function (data) {
          expect(data.toString()).to.eql('Hello from 8080');
        });

        res.on('end', function () {
          expect(events).to.contain('caronte:outgoing:web:begin');
          expect(events).to.contain('caronte:outgoing:web:end');
          source.close();
          proxyServer.close();
          done();
        });
      }).end();
    });
  });
});
<commit_msg>[tests] added tests for websockets<commit_after>var caronte = require('../lib/caronte'),
    expect  = require('expect.js'),
    http    = require('http'),
    ws      = require('ws');

describe('lib/caronte.js', function() {
  describe('#createProxyServer', function() {
    it('should throw without options', function() {
      var error;
      try {
        caronte.createProxyServer();
      } catch(e) {
        error = e;
      }

      expect(error).to.be.an(Error);
    })

    it('should return an object otherwise', function() {
      var obj = caronte.createProxyServer({
        target: 'http://www.google.com:80'
      });

      expect(obj.web).to.be.a(Function);
      expect(obj.ws).to.be.a(Function);
      expect(obj.listen).to.be.a(Function);
    });
  });

  describe('#createProxyServer with forward options and using web-incoming passes', function () {
    it('should pipe the request using web-incoming#stream method', function (done) {
      var proxy = caronte.createProxyServer({
        forward: 'http://127.0.0.1:8080'
      }).listen('8081')

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        source.close();
        proxy.close();
        done();
      });

      source.listen('8080');

      http.request('http://127.0.0.1:8081', function() {}).end();
    })
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should make the request on pipe and finish it', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }).listen('8081');

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('POST');
        expect(req.headers['x-forwarded-for']).to.eql('127.0.0.1');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        source.close();
        proxy.close();
        done();
      });

      source.listen('8080');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'POST',
        headers: {
          'x-forwarded-for': '127.0.0.1'
        }
      }, function() {}).end();
    });
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should make the request, handle response and finish it', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }).listen('8081');

      var source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        res.writeHead(200, {'Content-Type': 'text/plain'})
        res.end('Hello from ' + source.address().port);
      });

      source.listen('8080');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function(res) {
        expect(res.statusCode).to.eql(200);

        res.on('data', function (data) {
          expect(data.toString()).to.eql('Hello from 8080');
        });

        res.on('end', function () {
          source.close();
          proxy.close();
          done();
        });
      }).end();
    });
  });

  describe('#createProxyServer() method with error response', function () {
    it('should make the request and emit the error event', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      });

      proxy.ee.on('caronte:outgoing:web:error', function (err) {
        expect(err).to.be.an(Error);
        expect(err.code).to.be('ECONNREFUSED');
        proxyServer.close();
        done();
      })

      var proxyServer = proxy.listen('8081');

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function() {}).end();
    });
  });

  describe('#createProxyServer using the web-incoming passes', function () {
    it('should emit events correclty', function(done) {
      var proxy = caronte.createProxyServer({
        target: 'http://127.0.0.1:8080'
      }),

      proxyServer = proxy.listen('8081'),

      source = http.createServer(function(req, res) {
        expect(req.method).to.eql('GET');
        expect(req.headers.host.split(':')[1]).to.eql('8081');
        res.writeHead(200, {'Content-Type': 'text/plain'})
        res.end('Hello from ' + source.address().port);
      }),

      events = [];

      source.listen('8080');

      proxy.ee.on('caronte:**', function (uno, dos, tres) {
        events.push(this.event);
      })

      http.request({
        hostname: '127.0.0.1',
        port: '8081',
        method: 'GET',
      }, function(res) {
        expect(res.statusCode).to.eql(200);

        res.on('data', function (data) {
          expect(data.toString()).to.eql('Hello from 8080');
        });

        res.on('end', function () {
          expect(events).to.contain('caronte:outgoing:web:begin');
          expect(events).to.contain('caronte:outgoing:web:end');
          source.close();
          proxyServer.close();
          done();
        });
      }).end();
    });
  });

  describe('#createProxyServer using the ws-incoming passes', function () {
    it('should proxy the websockets stream', function (done) {
      var proxy = caronte.createProxyServer({
        target: 'ws://127.0.0.1:8080',
        ws: true
      }),
      proxyServer = proxy.listen('8081'),
      destiny = new ws.Server({ port: 8080 }, function () {
        var client = new ws('ws://127.0.0.1:8081');

        client.on('open', function () {
          client.send('hello there');
        });

        client.on('message', function (msg) {
          expect(msg).to.be('Hello over websockets');
          proxyServer.close();
          destiny.close();
          done();
        });
      });

      destiny.on('connection', function (socket) {
        socket.on('message', function (msg) {
          expect(msg).to.be('hello there');
          socket.send('Hello over websockets');
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */

describe('my app', function() {

  beforeEach(function() {
    browser().navigateTo('../app/index.html');
  });

  it('should automatically redirect to /jargon when location hash/fragment is empty', function() {
    expect(browser().location().url()).toBe("/jargon");
  });
});
<commit_msg>Still trying to get the e2e test to run<commit_after>'use strict';

/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */

describe('my app', function() {

  beforeEach(function() {
    browser().navigateTo('../../app/index.html');
  });

  it('should automatically redirect to /jargon when location hash/fragment is empty', function() {
    expect(browser().location().url()).toBe("/jargon");
  });
});
<|endoftext|>


<commit_before>var appresolver = require('../src/localAppResolver'),
	corsProxy = require('superagent-d2l-cors-proxy'),
	request = require('request');

var APP_CLASS = 'urn:d2l:fra:class:some-class';
var DEFAULT_PORT = 3000;

describe('localAppResolver', function() {

	describe('defaults', function() {
		it('should have _opts property', function() {
			appresolver().should.have.property('_opts').that.is.not.null;
		});

		it('class', function() {
			appresolver(APP_CLASS)._opts.should.have.property('appClass', APP_CLASS);
		});

		it('hostname', function() {
			appresolver()._opts.should.have.property('hostname', require('os').hostname());
		});

		it('port', function() {
			appresolver()._opts.should.have.property('port', DEFAULT_PORT);
		});

		it('dist', function() {
			appresolver()._opts.should.have.property('dist', 'dist');
		});

		it('appconfig', function() {
			appresolver()._opts.should.have.property('configFile', 'appconfig.json');
		});
	});

	describe('hostname', function() {
		it('should strip ".local" domain from OSX hostname', function() {
			appresolver(APP_CLASS, { hostname: 'somehost.local' })
				.getUrl().should.equal('http://somehost:' + DEFAULT_PORT + '/app/');
		});
	});

	describe('getUrl', function() {
		it('should return expected url', function(){
			appresolver(APP_CLASS, { hostname: 'somehost.com', port: 11111 })
				.getUrl().should.equal('http://somehost.com:11111/app/');
		});
	});

	describe('getConfigUrl', function() {
		it('should return expected url', function(){
			appresolver(APP_CLASS, { hostname: 'somehost.com', port: 11111, configFile: 'someconf.js' })
				.getConfigUrl().should.equal('http://somehost.com:11111/app/someconf.js');
		});
	});

	describe('host', function(){

		var resolver = appresolver(APP_CLASS, { dist: 'test/testDist', hostname: 'localhost' });
		resolver.host();

		it('should serve resolution', function(cb) {
			request.get('http://localhost:' + DEFAULT_PORT + '/resolve/' + encodeURIComponent(APP_CLASS), function(error, response, body){
				if(error) {
					cb(error);
				} else if ( response.statusCode != 200 ) {
					cb(response.statusCode);
				} else if ( JSON.parse(body).url != 'http://localhost:' + DEFAULT_PORT + '/app/appconfig.json' ) {
					cb(JSON.parse(body));
				} else {
					cb();
				}
			});
		});

		it('should serve static files', function(cb) {
			request.get('http://localhost:' + DEFAULT_PORT + '/app/staticFileToBeServed.txt', function(error, response, body){
				if(error) {
					cb(error);
				} else if ( response.statusCode != 200 ) {
					cb(response);
				} else if ( body != 'some simple contents' ) {
					cb(body);
				} else {
					cb();
				}
			});
		});

		it('should serve CORS proxy', function(cb) {
			var url = 'http://localhost:' + DEFAULT_PORT +
				corsProxy.getProxyDefaultLocation();
			request.get(url, function(err, res, body) {
				if(err)
					return cb(err);
				if(res.statusCode !== 200)
					return cb(res);
				cb();
			});
		});

	});

});
<commit_msg>Adding unit test to cover the case when local app resolver is called to resolve an app-class that is not being hosted.<commit_after>var appresolver = require('../src/localAppResolver'),
	corsProxy = require('superagent-d2l-cors-proxy'),
	request = require('request');

var APP_CLASS = 'urn:d2l:fra:class:some-class';
var DEFAULT_PORT = 3000;

describe('localAppResolver', function() {

	describe('defaults', function() {
		it('should have _opts property', function() {
			appresolver().should.have.property('_opts').that.is.not.null;
		});

		it('class', function() {
			appresolver(APP_CLASS)._opts.should.have.property('appClass', APP_CLASS);
		});

		it('hostname', function() {
			appresolver()._opts.should.have.property('hostname', require('os').hostname());
		});

		it('port', function() {
			appresolver()._opts.should.have.property('port', DEFAULT_PORT);
		});

		it('dist', function() {
			appresolver()._opts.should.have.property('dist', 'dist');
		});

		it('appconfig', function() {
			appresolver()._opts.should.have.property('configFile', 'appconfig.json');
		});
	});

	describe('hostname', function() {
		it('should strip ".local" domain from OSX hostname', function() {
			appresolver(APP_CLASS, { hostname: 'somehost.local' })
				.getUrl().should.equal('http://somehost:' + DEFAULT_PORT + '/app/');
		});
	});

	describe('getUrl', function() {
		it('should return expected url', function(){
			appresolver(APP_CLASS, { hostname: 'somehost.com', port: 11111 })
				.getUrl().should.equal('http://somehost.com:11111/app/');
		});
	});

	describe('getConfigUrl', function() {
		it('should return expected url', function(){
			appresolver(APP_CLASS, { hostname: 'somehost.com', port: 11111, configFile: 'someconf.js' })
				.getConfigUrl().should.equal('http://somehost.com:11111/app/someconf.js');
		});
	});

	describe('host', function(){

		var resolver = appresolver(APP_CLASS, { dist: 'test/testDist', hostname: 'localhost' });
		resolver.host();

		it('should serve resolution', function(cb) {
			request.get('http://localhost:' + DEFAULT_PORT + '/resolve/' + encodeURIComponent(APP_CLASS), function(error, response, body){
				if(error) {
					cb(error);
				} else if ( response.statusCode != 200 ) {
					cb(response.statusCode);
				} else if ( JSON.parse(body).url != 'http://localhost:' + DEFAULT_PORT + '/app/appconfig.json' ) {
					cb(JSON.parse(body));
				} else {
					cb();
				}
			});
		});

		it('should not serve resolution when trying to resolve app-class that is not being hosted', function(cb) {
			request.get('http://localhost:' + DEFAULT_PORT + '/resolve/some-other-app-class', function(error, response, body){
				if(error) {
					cb(error);
				} else if ( response.statusCode != 404 ) {
					console.log(response.statusCode);
				} else {
					cb();
				}
			});
		});

		it('should serve static files', function(cb) {
			request.get('http://localhost:' + DEFAULT_PORT + '/app/staticFileToBeServed.txt', function(error, response, body){
				if(error) {
					cb(error);
				} else if ( response.statusCode != 200 ) {
					cb(response);
				} else if ( body != 'some simple contents' ) {
					cb(body);
				} else {
					cb();
				}
			});
		});

		it('should serve CORS proxy', function(cb) {
			var url = 'http://localhost:' + DEFAULT_PORT +
				corsProxy.getProxyDefaultLocation();
			request.get(url, function(err, res, body) {
				if(err)
					return cb(err);
				if(res.statusCode !== 200)
					return cb(res);
				cb();
			});
		});

	});

});
<|endoftext|>


<commit_before>function random(min, max) {
    return Math.floor(Math.random()*(max-min+1)+min);
}

var HomePage = function() {
    this.meetingName = 'test' + random(1000,2000);

    this.meetingInput = element(by.model('meetingName')),
    this.createMeetingButton = element(by.buttonText('Create a Meeting!')),

    this.get = function() {
        browser.get('http://localhost:5000/');
    }

    this.createMeeting = function() {
        this.meetingInput.sendKeys(this.meetingName);
        this.createMeetingButton.click();
    }

    this.getMeetingName = function() {
        return this.meetingName;
    }
};

var MeetingPage = function() {
    this.userGreeting = function() {
        return element(by.binding('user'));
    }

    this.meetingListText = function() {
        $('.meeting-list').getText();
    }
};

describe('lean coffee', function(){

    var homePage = new HomePage();
    homePage.get();
    homePage.createMeeting();

    describe('meeting', function(){
        var meetingPage = new MeetingPage();

        it('should display my user name', function(){
            expect(meetingPage.userGreeting().getText()).toMatch(/You are: [\s\w]+/);
        });

        xit('should display the meeting particpants', function(){

        });

        it('should show the name of the meeting', function(){
            browser.debugger();

            expect(meetingPage.meetingListText()).toContain(homePage.getMeetingName());
        });

        xit('should show who the host is', function(){

        });

        xit('should show me if I am the host', function(){

        });

        xit('should allow transition to the next meeting phase', function(){

        });

        xit('should show a timer', function(){

        });

        describe('sumbit phase', function(){

            xit('should allow us submit a topic', function(){

            });

            xit('should show all topics', function(){

            });

            xit('should show the remaining time for the phase', function(){
            x
            });

            xit('should show the name of the person who submitted the topic', function(){

            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the merge phase', function(){

                });

                xit('(maybe?) should disable submission, but not automaticallly transition to merge phase', function(){

                });
            });
        });

        describe('merge phase', function(){

            xit('(maybe?) should allow merging of 2+ topics', function(){

            });

            xit('should allow us to merge two topics', function(){

            });

            describe('merge ownership', function(){

                xit('(maybe?) should show all owners for a merged topic', function(){

                });

                xit('(maybe?) should show the original topic owner', function(){

                });
            });

            describe('merge text', function(){

                xit('(maybe?) should merge text when topics are merged', function(){

                });

                xit('(maybe?) should show original text when topics are merged', function(){

                });
            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the vote phase', function(){

                });

                xit('(maybe?) should disable merging, but not automaticallly transition to voting phase', function(){

                });
            });
        });

        describe('voting phase', function(){

            xit('should start the timer when the phase begins', function(){

            });

            xit('should show the remaining time for the phase', function(){

            });

            xit('should display an icon to vote with', function(){

            });

            xit('should allow a user to vote', function(){

            });

            xit('should allow a user to vote up to 3 times', function(){

            });

            xit('should allow a user to vote on up to 3 topics', function(){

            });

            xit('should allow a user to vote 3 times on a single topic', function(){

            });

            xit('should not allow a user to vote 4 or more times', function(){

            });

            xit('should display the number of votes the user has remaining', function(){

            });

            xit('(maybe?) should display total number of remaining votes across all users', function(){

            });

            xit('(maybe?) should allow a user to change their votes', function(){

            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the discuss phase', function(){

                });

                xit('(maybe?) should disable voting, but not automatically move to the discuss phase', function(){

                });
            });
        });

        describe('discuss phase', function(){

            xit('should order the topics by number of votes', function(){

            });

            xit('should show the active topic', function(){

            });

            xit('should show the remaining time to discuss the active topic', function(){

            });

            xit('should allow the host to advance to the topic forcibly', function(){

            });

            describe('all topics discussed', function(){
                xit('(maybe?) should show star guy', function(){

                });

                xit('(maybe?) should show a summary of topics discussed', function(){

                });

                xit('should have no active topic', function(){

                });

                xit('should hide any remaining timers', function(){

                });

                xit('(maybe?) should prompt for email', function(){

                });

                xit('(maybe?) should move the user to the landing page and delete the meeting', function(){

                });
            });

            describe('timer expiration', function(){

                xit('(maybe?) should allow you to vote to continue discussing this topic', function(){

                });

                xit('(maybe?) should allow you to vote to continue discussing this topic 15 seconds prior to expiration', function(){

                });

                describe('vote is to stop talking about the current topic' , function(){

                    xit('(maybe?) automatically move to the next topic', function(){

                    });

                    xit('(maybe?) should do nothing', function(){

                    });

                    xit('(maybe?) it allows manual movement to the next topic (by host, or by user)', function(){

                    });

                });

                describe('vote is to continue talking' , function(){

                    xit('(maybe?) automatically reset the timer to 3 minutes', function(){

                    });

                    xit('(maybe?) should do nothing', function(){

                    });

                    xit('(maybe?) it allows manual reset of the timer', function(){

                    });

                });

                describe('the vote to continue is tied or no one votes', function(){
                    xit('(maybe?) host breaks the tie', function(){

                    });

                    xit('(maybe?) tie goes stop', function(){

                    });
                });

            });

            describe('tie breakers', function(){
                xit('(maybe?) should sort ties by timestamp', function(){

                });

                xit('(maybe?) should randomize ties', function(){

                });

                xit('(maybe?) should sort ties alphabetically', function(){

                });
            });

            describe('(maybe?) notes', function(){

                xit('(maybe?> allow host to take notes on active topic', function(){

                });

                xit('(maybe?) should allow participants to take personal notes', function(){

                });

                xit('(maybe?) should allow collaborative note taking', function(){

                });

                xit('(maybe?) should display the all notes to all participants', function(){

                });

                xit('(maybe?) should only allow the Chinese swear words from Firefly', function(){

                });
            });

            describe('email', function(){
                xit('(maybe?) should allow "subscribing" to each topic', function(){

                });

                xit('(maybe?) should populate an email subject and body', function(){

                });

                xit('should be optional', function(){

                });

                describe('body', function(){
                    xit('(maybe?) should contain all the topics discussed', function(){

                    });

                    xit('(maybe?) should contain only the topics "subscribed" to', function(){

                    });

                    xit('(maybe?) should contain the notes for each topic', function(){

                    });
                });
            });
        });
    });

});
<commit_msg>added page object for meeting<commit_after>function random(min, max) {
    return Math.floor(Math.random()*(max-min+1)+min);
}

var HomePage = function() {
    this.meetingName = 'test' + random(1000,2000);

    this.meetingInput = element(by.model('meetingName')),
    this.createMeetingButton = element(by.buttonText('Create a Meeting!')),

    this.get = function() {
        browser.get('http://localhost:5000/');
    }

    this.createMeeting = function() {
        this.meetingInput.sendKeys(this.meetingName);
        this.createMeetingButton.click();
    }

    this.getMeetingName = function() {
        return this.meetingName;
    }
};

var MeetingPage = function() {
    this.userGreeting = function() {
        return element(by.binding('user'));
    }

    this.meetingListText = function() {
        return $('.meeting-list').getText();
    }
};

describe('lean coffee', function(){

    var homePage = new HomePage();
    homePage.get();
    homePage.createMeeting();

    describe('meeting', function(){
        var meetingPage = new MeetingPage();

        it('should display my user name', function(){
            expect(meetingPage.userGreeting().getText()).toMatch(/You are: [\s\w]+/);
        });

        xit('should display the meeting particpants', function(){

        });

        it('should show the name of the meeting', function(){
            expect(meetingPage.meetingListText()).toContain(homePage.getMeetingName());
        });

        xit('should show who the host is', function(){

        });

        xit('should show me if I am the host', function(){

        });

        xit('should allow transition to the next meeting phase', function(){

        });

        xit('should show a timer', function(){

        });

        describe('sumbit phase', function(){

            xit('should allow us submit a topic', function(){

            });

            xit('should show all topics', function(){

            });

            xit('should show the remaining time for the phase', function(){
            x
            });

            xit('should show the name of the person who submitted the topic', function(){

            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the merge phase', function(){

                });

                xit('(maybe?) should disable submission, but not automaticallly transition to merge phase', function(){

                });
            });
        });

        describe('merge phase', function(){

            xit('(maybe?) should allow merging of 2+ topics', function(){

            });

            xit('should allow us to merge two topics', function(){

            });

            describe('merge ownership', function(){

                xit('(maybe?) should show all owners for a merged topic', function(){

                });

                xit('(maybe?) should show the original topic owner', function(){

                });
            });

            describe('merge text', function(){

                xit('(maybe?) should merge text when topics are merged', function(){

                });

                xit('(maybe?) should show original text when topics are merged', function(){

                });
            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the vote phase', function(){

                });

                xit('(maybe?) should disable merging, but not automaticallly transition to voting phase', function(){

                });
            });
        });

        describe('voting phase', function(){

            xit('should start the timer when the phase begins', function(){

            });

            xit('should show the remaining time for the phase', function(){

            });

            xit('should display an icon to vote with', function(){

            });

            xit('should allow a user to vote', function(){

            });

            xit('should allow a user to vote up to 3 times', function(){

            });

            xit('should allow a user to vote on up to 3 topics', function(){

            });

            xit('should allow a user to vote 3 times on a single topic', function(){

            });

            xit('should not allow a user to vote 4 or more times', function(){

            });

            xit('should display the number of votes the user has remaining', function(){

            });

            xit('(maybe?) should display total number of remaining votes across all users', function(){

            });

            xit('(maybe?) should allow a user to change their votes', function(){

            });

            describe('timer expiration', function(){

                xit('(maybe?) should advance automatically to the discuss phase', function(){

                });

                xit('(maybe?) should disable voting, but not automatically move to the discuss phase', function(){

                });
            });
        });

        describe('discuss phase', function(){

            xit('should order the topics by number of votes', function(){

            });

            xit('should show the active topic', function(){

            });

            xit('should show the remaining time to discuss the active topic', function(){

            });

            xit('should allow the host to advance to the topic forcibly', function(){

            });

            describe('all topics discussed', function(){
                xit('(maybe?) should show star guy', function(){

                });

                xit('(maybe?) should show a summary of topics discussed', function(){

                });

                xit('should have no active topic', function(){

                });

                xit('should hide any remaining timers', function(){

                });

                xit('(maybe?) should prompt for email', function(){

                });

                xit('(maybe?) should move the user to the landing page and delete the meeting', function(){

                });
            });

            describe('timer expiration', function(){

                xit('(maybe?) should allow you to vote to continue discussing this topic', function(){

                });

                xit('(maybe?) should allow you to vote to continue discussing this topic 15 seconds prior to expiration', function(){

                });

                describe('vote is to stop talking about the current topic' , function(){

                    xit('(maybe?) automatically move to the next topic', function(){

                    });

                    xit('(maybe?) should do nothing', function(){

                    });

                    xit('(maybe?) it allows manual movement to the next topic (by host, or by user)', function(){

                    });

                });

                describe('vote is to continue talking' , function(){

                    xit('(maybe?) automatically reset the timer to 3 minutes', function(){

                    });

                    xit('(maybe?) should do nothing', function(){

                    });

                    xit('(maybe?) it allows manual reset of the timer', function(){

                    });

                });

                describe('the vote to continue is tied or no one votes', function(){
                    xit('(maybe?) host breaks the tie', function(){

                    });

                    xit('(maybe?) tie goes stop', function(){

                    });
                });

            });

            describe('tie breakers', function(){
                xit('(maybe?) should sort ties by timestamp', function(){

                });

                xit('(maybe?) should randomize ties', function(){

                });

                xit('(maybe?) should sort ties alphabetically', function(){

                });
            });

            describe('(maybe?) notes', function(){

                xit('(maybe?> allow host to take notes on active topic', function(){

                });

                xit('(maybe?) should allow participants to take personal notes', function(){

                });

                xit('(maybe?) should allow collaborative note taking', function(){

                });

                xit('(maybe?) should display the all notes to all participants', function(){

                });

                xit('(maybe?) should only allow the Chinese swear words from Firefly', function(){

                });
            });

            describe('email', function(){
                xit('(maybe?) should allow "subscribing" to each topic', function(){

                });

                xit('(maybe?) should populate an email subject and body', function(){

                });

                xit('should be optional', function(){

                });

                describe('body', function(){
                    xit('(maybe?) should contain all the topics discussed', function(){

                    });

                    xit('(maybe?) should contain only the topics "subscribed" to', function(){

                    });

                    xit('(maybe?) should contain the notes for each topic', function(){

                    });
                });
            });
        });
    });

});
<|endoftext|>


<commit_before>$('#searchbox').on('input', function (data) {
  var searchTerm = data.target.value;
  console.log(searchTerm);

  var settings = {
    "async": true,
    "crossDomain": true,
    "url": "http://lov.okfn.org/dataset/lov/api/v2/term/search?q=" + searchTerm + "&page_size=100",
    "method": "GET",
  };

  function fillTableRows(result) {
    var urlParts = result.uri[0].split('/');
    if (urlParts.length == 3) { // URL is already a base url
      baseURL = result.uri[0];
    } else {
      baseURL = urlParts
        .slice(0, 3)
        .join('/');
    }

    var tableRow =
      '
'+ result.prefixedName[0] +
      '	' +
      ''+ baseURL +
      '
';

    if (result.uri[0].match('overheid.nl')) {
      $("#trustedResults tbody").append(tableRow);
    } else {
      $("#miscResults tbody").append(tableRow);
    }
  }

  $.ajax(settings).done(function (response) {
    $('#trustedResults tbody').empty();
    $('#miscResults tbody').empty();
    response.results.forEach(fillTableRows);
  });
});
<commit_msg>http -> https<commit_after>$('#searchbox').on('input', function (data) {
  var searchTerm = data.target.value;
  console.log(searchTerm);

  var settings = {
    "async": true,
    "crossDomain": true,
    "url": "https://lov.okfn.org/dataset/lov/api/v2/term/search?q=" + searchTerm + "&page_size=100",
    "method": "GET",
  };

  function fillTableRows(result) {
    var urlParts = result.uri[0].split('/');
    if (urlParts.length == 3) { // URL is already a base url
      baseURL = result.uri[0];
    } else {
      baseURL = urlParts
        .slice(0, 3)
        .join('/');
    }

    var tableRow =
      '
'+ result.prefixedName[0] +
      '	' +
      ''+ baseURL +
      '
';

    if (result.uri[0].match('overheid.nl')) {
      $("#trustedResults tbody").append(tableRow);
    } else {
      $("#miscResults tbody").append(tableRow);
    }
  }

  $.ajax(settings).done(function (response) {
    $('#trustedResults tbody').empty();
    $('#miscResults tbody').empty();
    response.results.forEach(fillTableRows);
  });
});
<|endoftext|>


<commit_before>
import fs from 'fs';
import path from 'path';
import net from 'net';
import {spawn} from 'child_process';

import chalk from 'chalk';

import * as _ from './helper';
import {
  CWD,
  JETTY_PATH
} from './constants';

/**
 * 检测是否安装Java
 * @return {Promise}
 */
function checkJava() {
  return new Promise((resolve, reject) => {
    let regex = /\b((?:\d+\.){2}\d+(?:_\d+)?)\b/,
      java = spawn('java', ['-version']),
      version = null,
      fail = () => {
        console.error(chalk.red('[×] Please install java in your PATH or set JAVA_HOME'));
        reject();
        process.exit(1);
      };

    java.stderr.on('data', (data) => {
      let ret = data.toString().match(regex);

      if (!version && ret) {
        version = ret[0];
        console.log(chalk.cyan('JRE version: ') + chalk.magenta('%s'), version);
        resolve();
      }
    });

    java
      .on('error', fail)
      .on('exit', () => {
        if (!version) {
          fail();
        }
      });
  });
}

/**
 * 检测端口是否可用
 * @param  {Number} port
 * @param  {String} name
 * @return {Promise}
 */
function checkPort(port, name) {
  return new Promise((resolve, reject) => {
    _.apps.find({port})
      .then((appList) => {
        let app = appList[0];

        if (app && app.name !== name) {
          reject(new Error(`The port ${port} is be used by ${app.name} app`));
          return;
        }

        // 测试端口是否可用
        let sniffer = net.connect({
          port
        }, () => {
          reject(new Error(`${port} already in use`));
          sniffer.destroy();
        });

        sniffer.once('error', () => {
          resolve(port);
          sniffer.destroy();
        });
      });
  });
}

/**
 * 检查参数
 * @return {Number}
 */
function checkArgs(args) {
  let {port, name, id} = args;

  if (port != null && (typeof port === 'boolean' || isNaN(port))) {
    console.error(chalk.red('[×] Invalid port'));
    return false;
  }

  if (name != null && typeof name !== 'string') {
    console.error(chalk.red('[×] Invalid app name'));
    return false;
  }

  if (id != null && (typeof id === 'boolean' || isNaN(id))) {
    console.error(chalk.red('[×] Invalid app id'));
    return false;
  }

  return true;
}

/**
 * 根据pid杀死指定进程
 * @param {Number}
 */
function kill(pid) {
  if (pid) {
    try {
      process.kill(pid, 'SIGKILL');
    } catch (err) {
      throw new Error(`pid(${pid}): process kill fail`);
    }
  }
}

/**
 * 纠正name，当name被认为是无效的值，则返回当前所在目录名
 * @param {String} name
 * @return {String}
 */
function correctName(name) {
  if (typeof name !== 'string' || name === '') {
    return path.basename(CWD);
  }

  return name;
}

/**
 * 启动jetty
 * @param  {Number} port
 * @param  {String} name
 * @return {Promise}
 */
function startJetty(port, name) {
  return new Promise((resolve, reject) => {
    if (fs.existsSync(JETTY_PATH)) {
      _.apps.find({name})
        .then((appList) => {
          let app = appList[0] || {};
          kill(app.pid);
        })
        .then(() => {
          let finished = false;

          let jetty = spawn('java', [
            '-jar', JETTY_PATH,
            '-w', CWD,
            '-p', port
          ], {
            detached: true
          });

          jetty.stderr.on('data', (data) => {
            let chunk = data.toString('utf8');

            // 当输出过异常或成功启动
            if (finished) return;

            if (/Server[^\r\n]+Started[^\r\n]+/i.test(chunk)) {
              finished = true;
              resolve(jetty.pid);
            }

            if (chunk.indexOf('Exception') > -1) {
              let err = new Error(chunk);

              finished = true;
              err.pid = jetty.pid;
              reject(err);
            }
          });

          jetty.on('error', reject);
        });

    } else {
      reject(new Error('The marmot has been corrupted! please reinstall the marmot'));
    }
  });
}

/**
 * 停止jetty
 * @param  {Number} port
 * @param  {String} name
 * @param  {Number} id
 * @return {Promise}
 */
function stopJetty(port, name, id) {
  return _.apps.find({port, name, id})
    .then((appList) => {
      for (let i = 0, app; app = appList[i++];) {
        if (app.pid !== '') {
          kill(app.pid);
          app.status = 'stopped';
          app.pid = '';
        }
      }

      return appList;
    })
    .then(_.apps.save);
}

/**
 * 启动应用
 * @todo 项目名未传的情况下会使用当前所在的目录名
 * @param {Number} port
 * @param {String} name
 */
export function start(port, name) {
  if (!checkArgs({port})) return;
  name = correctName(name);

  let resolver = Promise.resolve();

  // 未指定端口号的情况下
  if (port == null) {
    resolver = _.apps.find({name})
      .then((appList) => {
        let app = appList[0];

        if (app) {
          port = app.port;
        } else {
          port = 8080;
        }
      });
  }

  resolver
    .then(checkJava)
    .then(() => checkPort(port, name))
    .then(() => startJetty(port, name))
    .then(pid => new Promise((resolve, reject) => {
      _.apps.save({
        name,
        port,
        pid,
        status: 'online',
        pathname: CWD
      })
      .then(resolve)
      .catch((err) => {
        err.pid = pid;
        reject(err);
      });
    }))
    .then(() => {
      let ip = _.ip();

      console.log('');
      console.log('Access URLs:');
      console.log('----------------------');
      console.log(`   Local: ${chalk.magenta('http://%s:%s')}`, ip.internal, port);
      console.log(`External: ${chalk.magenta('http://%s:%s')}`, ip.external, port);
      console.log('');
      console.log(chalk.green('[√] %s app started successfully'), name);

      process.exit(0);
    })
    .catch((err) => {
      console.error(chalk.red('[×] Startup server has encountered a error'));
      if (err) {
        console.error(chalk.red('[×] %s'), err.message);
        kill(err.pid);
      }
      process.exit(1);
    });
}

/**
 * 停止应用
 * @param  {Number} port
 * @param  {String} name
 * @param  {Number} id
 */
export function stop(port, name, id) {
  if (!checkArgs({port, name, id})) return;

  if (port == null && name == null && id == null) {
    name = correctName(name);
  }

  checkJava()
    .then(() => stopJetty(port, name, id))
    .then((nameList) => {
      if (nameList.length) {
        console.log(chalk.green('[√] %s app stopped successfully'), nameList.join());
      } else {
        console.warn(chalk.yellow('[i] Didn\'t find any app can be stopped'));
      }
    })
    .catch((err) => {
      console.error(chalk.red('[×] Stopped server has encountered a error'));
      console.error(chalk.red('[×] %s'), err.message);
    });
}

/**
 * 删除应用
 * @param {Number} port
 * @param {String} name
 * @param {Number} id
 */
export function remove(port, name, id) {
  let opts = {port, name, id};

  if (!checkArgs(opts)) return;

  _.apps.find(opts)
    .then((appList) => {
      for (let i = 0, app; app = appList[i++];) {
        kill(app.pid);
      }

      return appList;
    })
    .then(_.apps.remove)
    .then((nameList) => {
      if (nameList.length) {
        console.log(chalk.green('[√] %s app removed success'), nameList.join());
      } else {
        console.warn(chalk.yellow('[i] Didn\'t find any app can be removed'));
      }
    })
    .catch((err) => {
      console.error(chalk.red('[×] %s'), err.message);
    });
}

/**
 * 显示所有应用列表
 */
export function list() {
  _.readAppsFile()
    .then((file) => {
      let body = [],
        appList = file.list;

      body = appList.map((p) => {

        p.name = chalk.cyan(p.name);

        if (p.status === 'online') {
          p.status = chalk.green(p.status);
        } else if (p.status === 'stopped') {
          p.status = chalk.red(p.status);
        }

        return [
          p.id,
          p.name,
          p.port,
          p.pid,
          p.status,
          p.pathname
        ];
      });

      _.printTable({
        head: ['id', 'name', 'port', 'pid', 'status', 'path'],
        body
      });
    })
    .catch((err) => {
      console.error(chalk.red('[×] %s'), err.message);
    });
}
<commit_msg>Bugfix<commit_after>
import fs from 'fs';
import path from 'path';
import net from 'net';
import {spawn} from 'child_process';

import chalk from 'chalk';

import * as _ from './helper';
import {
  CWD,
  JETTY_PATH
} from './constants';

/**
 * 检测是否安装Java
 * @return {Promise}
 */
function checkJava() {
  return new Promise((resolve, reject) => {
    let regex = /\b((?:\d+\.){2}\d+(?:_\d+)?)\b/,
      java = spawn('java', ['-version']),
      version = null,
      fail = () => {
        console.error(chalk.red('[×] Please install java in your PATH or set JAVA_HOME'));
        reject();
        process.exit(1);
      };

    java.stderr.on('data', (data) => {
      let ret = data.toString().match(regex);

      if (!version && ret) {
        version = ret[0];
        console.log(chalk.cyan('JRE version: ') + chalk.magenta('%s'), version);
        resolve();
      }
    });

    java
      .on('error', fail)
      .on('exit', () => {
        if (!version) {
          fail();
        }
      });
  });
}

/**
 * 检测端口是否可用
 * @param  {Number} port
 * @param  {String} name
 * @return {Promise}
 */
function checkPort(port, name) {
  return new Promise((resolve, reject) => {
    _.apps.find({port})
      .then((appList) => {
        let app = appList[0];

        if (app && app.name !== name) {
          reject(new Error(`The port ${port} is be used by ${app.name} app`));
          return;
        }

        // 测试端口是否可用
        let sniffer = net.connect({
          port
        }, () => {
          reject(new Error(`${port} already in use`));
          sniffer.destroy();
        });

        sniffer.once('error', () => {
          resolve(port);
          sniffer.destroy();
        });
      })
      .catch(reject);
  });
}

/**
 * 检查参数
 * @return {Number}
 */
function checkArgs(args) {
  let {port, name, id} = args;

  if (port != null && (typeof port === 'boolean' || isNaN(port))) {
    console.error(chalk.red('[×] Invalid port'));
    return false;
  }

  if (name != null && typeof name !== 'string') {
    console.error(chalk.red('[×] Invalid app name'));
    return false;
  }

  if (id != null && (typeof id === 'boolean' || isNaN(id))) {
    console.error(chalk.red('[×] Invalid app id'));
    return false;
  }

  return true;
}

/**
 * 根据pid杀死指定进程
 * @param {Number}
 */
function kill(pid) {
  if (pid) {
    try {
      process.kill(pid, 'SIGKILL');
    } catch (err) {}
  }
}

/**
 * 纠正name，当name被认为是无效的值，则返回当前所在目录名
 * @param {String} name
 * @return {String}
 */
function correctName(name) {
  if (typeof name !== 'string' || name === '') {
    return path.basename(CWD);
  }

  return name;
}

/**
 * 启动jetty
 * @param  {Number} port
 * @param  {String} name
 * @return {Promise}
 */
function startJetty(port, name) {
  return new Promise((resolve, reject) => {
    if (fs.existsSync(JETTY_PATH)) {
      _.apps.find({name})
        .then((appList) => {
          let app = appList[0] || {};
          kill(app.pid);
        })
        .then(() => {
          let finished = false;

          let jetty = spawn('java', [
            '-jar', JETTY_PATH,
            '-w', CWD,
            '-p', port
          ], {
            detached: true
          });

          jetty.stderr.on('data', (data) => {
            let chunk = data.toString('utf8');

            // 当输出过异常或成功启动
            if (finished) return;

            if (/Server[^\r\n]+Started[^\r\n]+/i.test(chunk)) {
              finished = true;
              resolve(jetty.pid);
            }

            if (chunk.indexOf('Exception') > -1) {
              let err = new Error(chunk);

              finished = true;
              err.pid = jetty.pid;
              reject(err);
            }
          });

          jetty.on('error', reject);
        })
        .catch(reject);

    } else {
      reject(new Error('The marmot has been corrupted! please reinstall the marmot'));
    }
  });
}

/**
 * 停止jetty
 * @param  {Number} port
 * @param  {String} name
 * @param  {Number} id
 * @return {Promise}
 */
function stopJetty(port, name, id) {
  return _.apps.find({port, name, id})
    .then((appList) => {
      for (let i = 0, app; app = appList[i++];) {
        if (app.pid !== '') {
          kill(app.pid);
          app.status = 'stopped';
          app.pid = '';
        }
      }

      return appList;
    })
    .then(_.apps.save);
}

/**
 * 启动应用
 * @todo 项目名未传的情况下会使用当前所在的目录名
 * @param {Number} port
 * @param {String} name
 */
export function start(port, name) {
  if (!checkArgs({port})) return;
  name = correctName(name);

  let resolver = Promise.resolve();

  // 未指定端口号的情况下
  if (port == null) {
    resolver = _.apps.find({name})
      .then((appList) => {
        let app = appList[0];

        if (app) {
          port = app.port;
        } else {
          port = 8080;
        }
      });
  }

  resolver
    .then(checkJava)
    .then(() => checkPort(port, name))
    .then(() => startJetty(port, name))
    .then(pid => new Promise((resolve, reject) => {
      _.apps.save({
        name,
        port,
        pid,
        status: 'online',
        pathname: CWD
      })
      .then(resolve)
      .catch((err) => {
        err.pid = pid;
        reject(err);
      });
    }))
    .then(() => {
      let ip = _.ip();

      console.log('');
      console.log('Access URLs:');
      console.log('----------------------');
      console.log(`   Local: ${chalk.magenta('http://%s:%s')}`, ip.internal, port);
      console.log(`External: ${chalk.magenta('http://%s:%s')}`, ip.external, port);
      console.log('');
      console.log(chalk.green('[√] %s app started successfully'), name);

      process.exit(0);
    })
    .catch((err) => {
      console.error(chalk.red('[×] Startup server has encountered a error'));
      if (err) {
        console.error(chalk.red('[×] %s'), err.message);
        kill(err.pid);
      }
      process.exit(1);
    });
}

/**
 * 停止应用
 * @param  {Number} port
 * @param  {String} name
 * @param  {Number} id
 */
export function stop(port, name, id) {
  if (!checkArgs({port, name, id})) return;

  if (port == null && name == null && id == null) {
    name = correctName(name);
  }

  checkJava()
    .then(() => stopJetty(port, name, id))
    .then((nameList) => {
      if (nameList.length) {
        console.log(chalk.green('[√] %s app stopped successfully'), nameList.join());
      } else {
        console.warn(chalk.yellow('[i] Didn\'t find any app can be stopped'));
      }
    })
    .catch((err) => {
      console.error(chalk.red('[×] Stopped server has encountered a error'));
      console.error(chalk.red('[×] %s'), err.message);
    });
}

/**
 * 删除应用
 * @param {Number} port
 * @param {String} name
 * @param {Number} id
 */
export function remove(port, name, id) {
  let opts = {port, name, id};

  if (!checkArgs(opts)) return;

  _.apps.find(opts)
    .then((appList) => {
      for (let i = 0, app; app = appList[i++];) {
        kill(app.pid);
      }

      return appList;
    })
    .then(_.apps.remove)
    .then((nameList) => {
      if (nameList.length) {
        console.log(chalk.green('[√] %s app removed success'), nameList.join());
      } else {
        console.warn(chalk.yellow('[i] Didn\'t find any app can be removed'));
      }
    })
    .catch((err) => {
      console.error(chalk.red('[×] %s'), err.message);
    });
}

/**
 * 显示所有应用列表
 */
export function list() {
  _.readAppsFile()
    .then((file) => {
      let body = [],
        appList = file.list;

      body = appList.map((p) => {

        p.name = chalk.cyan(p.name);

        if (p.status === 'online') {
          p.status = chalk.green(p.status);
        } else if (p.status === 'stopped') {
          p.status = chalk.red(p.status);
        }

        return [
          p.id,
          p.name,
          p.port,
          p.pid,
          p.status,
          p.pathname
        ];
      });

      _.printTable({
        head: ['id', 'name', 'port', 'pid', 'status', 'path'],
        body
      });
    })
    .catch((err) => {
      console.error(chalk.red('[×] %s'), err.message);
    });
}
<|endoftext|>


<commit_before>'use strict';

/* https://github.com/angular/protractor/blob/master/docs/toc.md */

describe('udadisi app', function() {

  it('should automatically redirect to /home when location hash/fragment is empty', function() {
    browser.get('app/index.html');
    expect(browser.getLocationAbsUrl()).toMatch("/home");
  });

  describe('home', function() {
    beforeEach(function() {
      browser.get('app/#/home');
    });

    it('should render home when user navigates to /home', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/EXPLORE THE TRENDS/);
    });
  });

  describe('trend explorer', function() {
    beforeEach(function() {
      browser.get('app/#/trend-explorer');
    });

    it('should render trend explorer when user navigates to /trend-explorer', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/TREND EXPLORER/);
    });
  });

  describe('location profile', function() {
    beforeEach(function() {
      browser.get('app/#/locations/global');
    });

    it('should render global location profile when user navigates to /locations/global', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/GLOBAL TRENDS/);
    });
  });

  describe('trend profile', function() {
    beforeEach(function() {
      browser.get('app/#/trends/solar');
    });

    it('should render trend profile for "solar" when user navigates to /trends/mobile', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/SOLAR/);
    });
  });

});
<commit_msg>adds e2e test for listing trends<commit_after>'use strict';

/* https://github.com/angular/protractor/blob/master/docs/toc.md */

describe('udadisi app', function() {

  it('should automatically redirect to /home when location hash/fragment is empty', function() {
    browser.get('app/index.html');
    expect(browser.getLocationAbsUrl()).toMatch("/home");
  });

  describe('home', function() {
    beforeEach(function() {
      browser.get('app/#/home');
    });

    it('should render home when user navigates to /home', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/EXPLORE THE TRENDS/);
    });

    it('should show a list of global trends when user navigates to /home', function() {
      expect(element.all(by.css('ul.trends li')).count()).toBeGreaterThan(0);
    });
  });

  describe('trend explorer', function() {
    beforeEach(function() {
      browser.get('app/#/trend-explorer');
    });

    it('should render trend explorer when user navigates to /trend-explorer', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/TREND EXPLORER/);
    });
  });

  describe('location profile', function() {
    beforeEach(function() {
      browser.get('app/#/locations/global');
    });

    it('should render global location profile when user navigates to /locations/global', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/GLOBAL TRENDS/);
    });
  });

  describe('trend profile', function() {
    beforeEach(function() {
      browser.get('app/#/trends/solar');
    });

    it('should render trend profile for "solar" when user navigates to /trends/mobile', function() {
      expect(element.all(by.css('[ng-view] h1')).first().getText()).
        toMatch(/SOLAR/);
    });
  });

});
<|endoftext|>


<commit_before>'use strict';

var sinon = require('sinon')
  , request = require('request')
  , assert = require('assert')
  , logger = require('../lib/logger')
;

describe('middleware', function() {
  before(function() {
    sinon.stub(logger, 'info');
    sinon.stub(logger, 'verbose');
    sinon.stub(logger, 'debug');
  });
  after(function() {
    logger.info.restore();
    logger.debug.restore();
    logger.verbose.restore();
  });

  it('api', function(done) {
    sinon
      .stub(request, 'post')
      .yields(null, {statusCode: 200}, 'STORED;')
    ;

    var middleware = require('../lib/middleware.api');
    middleware(
      {
        api: 'testapi'
        , json: {from: 'from@ehlo.io'}
      }
      , {}
      , function apiCallback() {
        assert.strictEqual(request.post.callCount, 1);
        var args = request.post.args[0][0];
        assert.strictEqual(args.url, 'testapi');
        assert.strictEqual(
          args.formData.mail
          , JSON.stringify({from: 'from@ehlo.io'})
        );

        request
          .post
          .restore()
        ;
        done();
      }
    );
  });

  it('parse', function(done) {
    var middleware = require('../lib/middleware.parse')
      , mail = {};
    mail.raw = require('fs').readFileSync('./test/fixtures/mail1.eml');
    middleware(mail, {}, function parseCallback() {
      assert.strictEqual(mail.json.text, 'Hello !\n');
      assert.strictEqual(mail.json.headers.from, 'from@ehlo.io');
      assert.strictEqual(mail.json.headers.to, 'to@ehlo.io');
      assert.strictEqual(mail.json.headers['message-id'], '<UUID@EHLO.IO>');
      assert.strictEqual(mail.json.messageId, 'UUID@EHLO.IO');
      assert.strictEqual(mail.json.headers.subject, 'SAMPLE');
      assert.strictEqual(mail.json.subject, 'SAMPLE');
      assert.strictEqual(mail.json.priority, 'normal');
      assert.strictEqual(mail.json.from[0].address, 'from@ehlo.io');
      assert.strictEqual(mail.json.from[0].name, '');
      assert.strictEqual(mail.json.to[0].address, 'to@ehlo.io');
      assert.strictEqual(mail.json.to[0].name, '');
      done();
    });
  });

  it('smtp return 250', function(done) {
    var middleware = require('../lib/middleware.smtp.250');
    middleware(
      {}
      , {
        send: function() {
          done();
        }
      }
    );
  });

  it('smtp return 421', function(done) {
    var middleware = require('../lib/middleware.smtp.421');
    middleware(
      {}
      , {
        send: function(code, message) {
          assert.strictEqual(code, 421);
          assert.equal(message, 'Try again later');
          done();
        }
      }
    );
  });

  it('smtp return 550', function(done) {
    var middleware = require('../lib/middleware.smtp.550');
    middleware(
      {}
      , {
        send: function(code, message) {
          assert.strictEqual(code, 550);
          assert.equal(message, 'Mailbox unavailable');
          done();
        }
      }
    );
  });
});
<commit_msg>Mock smtp.session.id<commit_after>'use strict';

var sinon = require('sinon')
  , request = require('request')
  , assert = require('assert')
  , logger = require('../lib/logger')
;

describe('middleware', function() {
  before(function() {
    sinon.stub(logger, 'info');
    sinon.stub(logger, 'verbose');
    sinon.stub(logger, 'debug');
  });
  after(function() {
    logger.info.restore();
    logger.debug.restore();
    logger.verbose.restore();
  });

  it('api', function(done) {
    sinon
      .stub(request, 'post')
      .yields(null, {statusCode: 200}, 'STORED;')
    ;

    var middleware = require('../lib/middleware.api');
    middleware(
      {
        api: 'testapi'
        , json: {from: 'from@ehlo.io'}
      }
      , {session: {id: 'test'}}
      , function apiCallback() {
        assert.strictEqual(request.post.callCount, 1);
        var args = request.post.args[0][0];
        assert.strictEqual(args.url, 'testapi');
        assert.strictEqual(
          args.formData.mail
          , JSON.stringify({from: 'from@ehlo.io'})
        );

        request
          .post
          .restore()
        ;
        done();
      }
    );
  });

  it('parse', function(done) {
    var middleware = require('../lib/middleware.parse')
      , mail = {};
    mail.raw = require('fs').readFileSync('./test/fixtures/mail1.eml');
    middleware(mail, {}, function parseCallback() {
      assert.strictEqual(mail.json.text, 'Hello !\n');
      assert.strictEqual(mail.json.headers.from, 'from@ehlo.io');
      assert.strictEqual(mail.json.headers.to, 'to@ehlo.io');
      assert.strictEqual(mail.json.headers['message-id'], '<UUID@EHLO.IO>');
      assert.strictEqual(mail.json.messageId, 'UUID@EHLO.IO');
      assert.strictEqual(mail.json.headers.subject, 'SAMPLE');
      assert.strictEqual(mail.json.subject, 'SAMPLE');
      assert.strictEqual(mail.json.priority, 'normal');
      assert.strictEqual(mail.json.from[0].address, 'from@ehlo.io');
      assert.strictEqual(mail.json.from[0].name, '');
      assert.strictEqual(mail.json.to[0].address, 'to@ehlo.io');
      assert.strictEqual(mail.json.to[0].name, '');
      done();
    });
  });

  it('smtp return 250', function(done) {
    var middleware = require('../lib/middleware.smtp.250');
    middleware(
      {}
      , {
        send: function() {
          done();
        }
      }
    );
  });

  it('smtp return 421', function(done) {
    var middleware = require('../lib/middleware.smtp.421');
    middleware(
      {}
      , {
        send: function(code, message) {
          assert.strictEqual(code, 421);
          assert.equal(message, 'Try again later');
          done();
        }
      }
    );
  });

  it('smtp return 550', function(done) {
    var middleware = require('../lib/middleware.smtp.550');
    middleware(
      {}
      , {
        send: function(code, message) {
          assert.strictEqual(code, 550);
          assert.equal(message, 'Mailbox unavailable');
          done();
        }
      }
    );
  });
});
<|endoftext|>