<commit_before>//Jquery selector to return everything which has a model property set
$.expr[':'].dataModel = function(obj){
  var $this = $(obj);
  return (!!$this.data('model'));
};

/**
 *  You can have the same element listening to multiple model variables by seperating them using the "|" operator
 */
F.Decisionize = (function(){
    //Updating UI
    var populate = function(animate){
        if(animate !== false) animate = true;
        var queryList = [];

        // {decisionName: [elem1, elem2]}
        var valueElementList = {};

        //Get all displayed OP variables;
        $(":dataModel").each(function(){
            var modelVals = $(this).data("model").split("|");

            var elem = this;
            $.each(modelVals, function(index, val){
                val = val.replace(/D_/i, "");
                if(val.indexOf("=")!== -1) val = val.split("=")[0];

                var normalized = $.trim(F.Template.compile(val.toLowerCase()));

                if(!valueElementList[normalized]){
                    valueElementList[normalized] = [];
                }
                valueElementList[normalized].push(elem);

                if(!F.Array.contains(val, queryList))
                    queryList.push(val);
            })
        });

        //Get Data from Run API
        F.API.Run.getValues(queryList, function(run){
            $.each(run.values, function(key, value){
                //var listenersForThisVal = (valueElementList[key]) ? valueElementList[key] : [];
                var listenersForThisVal = valueElementList[key];
                //console.log("listenersForThisVal", listenersForThisVal, key, value);

                var formattedVal = (value.decisionFormatted) ? value.decisionFormatted : value.resultFormatted;
                var actualVal = (value.decision) ? value.decision : value.result;

                var isChanged = function(elem, valsObj){
                    var nodename = elem.nodeName.toLowerCase();
                    var type = (elem.type) ? elem.type.toLowerCase() : "";

                    var val = (valsObj.decision) ? valsObj.decision: valsObj.result;
                        val = parseFloat(val);

                    var formattedVal = (valsObj.decisionFormatted) ? valsObj.decisionFormatted: valsObj.resultFormatted;

                    var action = $(elem).data("action") || "";
                    var isUpdate =
                        (action.indexOf("update") !== -1 || action.indexOf("noop") === -1);

                    var isToggle =
                        (action.indexOf("show") !== -1) || (action.indexOf("hide") !== -1);

                    var isChange = false;
                    if(isUpdate){
                        switch(nodename){
                            case "input":
                                var elemVal = $.trim($(elem).val());
                                var elemValNo = parseFloat($(elem).val());
                                switch(type){
                                    case "radio":
                                        isChange = (!elem.checked && val === elemValNo);
                                        break;
                                    case "checkbox":
                                        isChange = (elem.checked && val !== elemValNo) || (!elem.checked && val === elemValNo);
                                        break;
                                    default:
                                        isChange = (elemVal !== formattedVal);
                                }
                                break;
                            case "select":
                                var elemValNo = parseFloat($(elem).val());
                                isChange = (elemValNo !== val);
                                break;
                            case "textarea":
                                var elemVal = $.trim($(elem).val());
                                isChange = (elemVal !== formattedVal);
                                break;
                            default:
                                var elemVal = $.trim($(elem).text());
                                isChange = (elemVal !== formattedVal);
                        }
                    }
                    if(isToggle){
                        var elemName = $(elem).data("model").split("=")[0].toLowerCase();
                        var elemVal = $(elem).data("model").split("=")[1];

                        var isMatch = (parseFloat(elemVal) == val);
                        var isHidden = ($(elem).hasClass("hidden"));

                        isChange = (isMatch && isHidden) || (!isMatch && !isHidden);

                        if(action.indexOf("hide") !== -1) isChange = !isChange;
                        //console.log("change", elem, val, action, isMatch, isHidden, isChange)
                    }

                    //console.log("checking", elem, isChange)
                    return isChange;
                }
                $.each(listenersForThisVal, function(index, listener){
                    if(isChanged(listener, value)){
                        $(listener).trigger("modelChange", {value: value, run:run, animate: animate});
                        //console.log("Triggering model change:", listener, value.label, key);
                    }
                });
            });
        });
    }

    var save = function(elem, oldvalue){
        var key = $(elem).data("model") ? $(elem).data("model") : $(elem).attr("name");
        if(key.toLowerCase().indexOf("d_") == 0){//Save in Run API
            F.API.Run.saveValues(elem, populate, null, {onError: function(error, actualError, responseText){
                responseText = $.parseJSON(responseText);
                if(responseText.errors){
                    $.each(responseText.errors, function(index, error){
                        var name = error.name.toLowerCase();
                        var type = error.type.toUpperCase();

                        if(type === "ABOVE_MAX" || type === "BELOW_MIN"){
                            if(error.decisionMax || error.decisionMax === 0)  $(elem).attr("max", error.decisionMax);
                            if(error.decisionMin || error.decisionMin === 0)  $(elem).attr("min", error.decisionMin);

                            $(elem).trigger("change.d", {oldvalue:oldvalue}); //Let the client-side validation kick in
                        }
                        else if(type === "PARSE_ERROR"){

                        }
                    })
                }
            }});
        }
        else{
            //Save in Data API. TODO: I'm saving but not reading from it right now
            F.API.Data.save(elem, populate);
        }
    }

    //Handles on change events of UI elemts
    var uiChangeHandler = {
        base: function(){
            //MakeQueryString has logic for not saving a blank radio button, checkboxes etc, so just pass it through
            save(this);
        },
        radio: function () {
            if (this.checked)   save(this);
        },
        textFocus: function(){
            $(this).data("valid", $(this).val());
        },
        text:  function(evt, data){
            var $elem = $(this);

            var min = $elem.attr("min") || $elem.data("min");
            var max = $elem.attr("max") || $elem.data("max");

            min = parseFloat(min);
            max = parseFloat(max);

            var val = F.Number.extract($elem.val());
            var prevData = (data && data.oldvalue)? data.oldvalue : $elem.data("valid");

            if(isNaN(val)){
                $elem.val(prevData);
                $elem.trigger("validationFailed",
                    {value: val, type: "invalid-entry", text: "Invalid Entry. Please enter a Number."});
            }
            else if(((min || min === 0) && val < parseFloat(min)) || ((max || max === 0) && val > parseFloat(max))){
                var msg, type;
                $elem.val(prevData);

                if((min || min === 0) && val < parseFloat(min)){
                    msg = "Please enter a value greater than " + min;
                    type = "min";
                }
                else{
                    msg = "Please enter a value less than " + max;
                    type = "max";
                }

                $elem.trigger("validationFailed",  {value: val, type: type, text: msg, min: min, max: max});
            }
            else{
                save(this, prevData);
                $elem.data("valid", $elem.val());
            }
        }
    }

    //Handles model updated events and updates ui elements
    var modelChangeHandler = function(){
            var getVal = function(valsObj){
                var val = (valsObj.decision) ? valsObj.decision: valsObj.result;
                return parseFloat(val);
            }
            var getFormattedVal = function(valsObj){
                return (valsObj.decisionFormatted) ? valsObj.decisionFormatted: valsObj.resultFormatted;
            }
            var matchVals = function(elem, valsObj){
                var actualVal = getVal(valsObj);
                var checkVal, valName;
                try{
                    checkVal = parseFloat($(elem).data("model").split("=")[1]);
                }
                catch (e){
                    throw new Error("To use show/hide the model should be in <modelname=value> format");
                }
                return (actualVal === checkVal);
            }

            var actionHandler = function(elem, type, params){
                var actions = ($(elem).data("action")) || "";
                    actions = actions.split(",");

                if(!F.Array.contains("noop", actions)){
                    actions.push("update");
                }
                //console.log("Actions found", actions, elem);
                //console.log("Triggered", elem, type, params.value.label, actions);
                //TODO: Make this configureable
                var CHANGE_CLASS = "changed";
                var HIDDEN_CLASS = "hidden";

                var defaultActionList = function(){
                    var toggle = function(isShow, elem, params){
                        var elemmodelName = $(elem).data("model").split("=")[0].toLowerCase();
                        var modelName = params.value.label.toLowerCase();

                        if(modelName === elemmodelName){
                            if((isShow && matchVals(elem, params.value)) || (!isShow && !matchVals(elem, params.value))){
                                $(elem).removeClass(HIDDEN_CLASS);
                                //console.log("removing", HIDDEN_CLASS, "from", elem, params)
                            }
                            else{
                                //console.log("adding", HIDDEN_CLASS, "to", elem, params)
                                $(elem).addClass(HIDDEN_CLASS);
                            }
                        }
                    }
                    return{
                        update: function(params){
                            switch(type){
                                case "radio":
                                case "checkbox":
                                    elem.checked = !elem.checked;
                                    break;
                                case "text":
                                    $(elem).val(getFormattedVal(params.value));
                                    break;
                                default:
                                    $(elem).text(getFormattedVal(params.value));
                            }
                            if(params.animate){
                                $(elem).addClass(CHANGE_CLASS);
                                setTimeout(function(){$(elem).removeClass(CHANGE_CLASS)}, 1500)
                            }
                        },
                        show: function(params){
                            return toggle(true, elem, params);
                        },
                        hide:  function(params){
                            return toggle(false, elem, params);
                        },
                        noop: $.noop
                    }
                }()

                $.each(actions, function(index, action){
                    action = $.trim(action);
                    if(action){
                        if(defaultActionList[action]){
                            defaultActionList[action](params);
                        }
                        else{
                            throw new Error("No default action found for " + action);
                        }
                    }

                })
            }
        return{
            radio: function(event, params){
                actionHandler(this, "radio", params)
            },
            text: function(event, params){
                actionHandler(this, "text", params)
            },
            base: function(event, params){
                actionHandler(this, "base", params);
            }
        }
    }()
    return{
        /**
         * Gets values from Run API and populates data for everything on the page with a defined model
         */
        populateUI: populate,
        init: function(selector){
            $(function(){
                selector || (selector = "body");
                selector+= " ";

                $(selector)
                    .off("click.d keyup.d blur.d change.d focus.d modelChange.default", "**")
                    .on("change.d", ":radio:dataModel", uiChangeHandler.radio) //Need change not click because can be triggered by clickin on labels
                    .on("blur.d", "textarea:dataModel", uiChangeHandler.base)
                    .on("change.d", ":checkbox:dataModel", uiChangeHandler.base)
                    .on("change.d", "select:dataModel", uiChangeHandler.base)
                    .on("keyup.d", ":text.autoformat", function(){
                        var format = $(this).data("format");
                        if(!format) return false;

                        var formatted = F.Number.format(F.Number.extract($(this).val()), format);
                        if(formatted !== "?"){
                            $(this).val(formatted);
                        }
                    })
                    .on("change.d", "input[type='range']:dataModel", uiChangeHandler.base)

                    .on("focus.d", ":text:dataModel,input[type='number']:dataModel", uiChangeHandler.textFocus)
                    .on("change.d", ":text:dataModel:not(.autoformat), input[type='number']:dataModel", uiChangeHandler.text)
                    .on("blur.d", ":text:dataModel.autoformat, input[type='number']:dataModel", uiChangeHandler.text)

                    .on("modelChange.default", ":radio:dataModel,:checkbox:dataModel", modelChangeHandler.radio)
                    .on("modelChange.default", "textarea:dataModel, :text:dataModel," +
                            "select:dataModel, input[type='number']:dataModel", modelChangeHandler.text)
                    .on("modelChange.default", ":dataModel:not(input,select,textarea)", modelChangeHandler.base)
            })
        }
    }
}())
<commit_msg>fixed decisionze so wrongly formatted numbers get corrected before being posted to simulate<commit_after>//Jquery selector to return everything which has a model property set
$.expr[':'].dataModel = function(obj){
  var $this = $(obj);
  return (!!$this.data('model'));
};

/**
 *  You can have the same element listening to multiple model variables by seperating them using the "|" operator
 */
F.Decisionize = (function(){
    //Updating UI
    var populate = function(animate){
        if(animate !== false) animate = true;
        var queryList = [];

        // {decisionName: [elem1, elem2]}
        var valueElementList = {};

        //Get all displayed OP variables;
        $(":dataModel").each(function(){
            var modelVals = $(this).data("model").split("|");

            var elem = this;
            $.each(modelVals, function(index, val){
                val = val.replace(/D_/i, "");
                if(val.indexOf("=")!== -1) val = val.split("=")[0];

                var normalized = $.trim(F.Template.compile(val.toLowerCase()));

                if(!valueElementList[normalized]){
                    valueElementList[normalized] = [];
                }
                valueElementList[normalized].push(elem);

                if(!F.Array.contains(val, queryList))
                    queryList.push(val);
            })
        });

        //Get Data from Run API
        F.API.Run.getValues(queryList, function(run){
            $.each(run.values, function(key, value){
                //var listenersForThisVal = (valueElementList[key]) ? valueElementList[key] : [];
                var listenersForThisVal = valueElementList[key];
                //console.log("listenersForThisVal", listenersForThisVal, key, value);

                var formattedVal = (value.decisionFormatted) ? value.decisionFormatted : value.resultFormatted;
                var actualVal = (value.decision) ? value.decision : value.result;

                var isChanged = function(elem, valsObj){
                    var nodename = elem.nodeName.toLowerCase();
                    var type = (elem.type) ? elem.type.toLowerCase() : "";

                    var val = (valsObj.decision) ? valsObj.decision: valsObj.result;
                        val = parseFloat(val);

                    var formattedVal = (valsObj.decisionFormatted) ? valsObj.decisionFormatted: valsObj.resultFormatted;

                    var action = $(elem).data("action") || "";
                    var isUpdate =
                        (action.indexOf("update") !== -1 || action.indexOf("noop") === -1);

                    var isToggle =
                        (action.indexOf("show") !== -1) || (action.indexOf("hide") !== -1);

                    var isChange = false;
                    if(isUpdate){
                        switch(nodename){
                            case "input":
                                var elemVal = $.trim($(elem).val());
                                var elemValNo = parseFloat($(elem).val());
                                switch(type){
                                    case "radio":
                                        isChange = (!elem.checked && val === elemValNo);
                                        break;
                                    case "checkbox":
                                        isChange = (elem.checked && val !== elemValNo) || (!elem.checked && val === elemValNo);
                                        break;
                                    default:
                                        isChange = (elemVal !== formattedVal);
                                }
                                break;
                            case "select":
                                var elemValNo = parseFloat($(elem).val());
                                isChange = (elemValNo !== val);
                                break;
                            case "textarea":
                                var elemVal = $.trim($(elem).val());
                                isChange = (elemVal !== formattedVal);
                                break;
                            default:
                                var elemVal = $.trim($(elem).text());
                                isChange = (elemVal !== formattedVal);
                        }
                    }
                    if(isToggle){
                        var elemName = $(elem).data("model").split("=")[0].toLowerCase();
                        var elemVal = $(elem).data("model").split("=")[1];

                        var isMatch = (parseFloat(elemVal) == val);
                        var isHidden = ($(elem).hasClass("hidden"));

                        isChange = (isMatch && isHidden) || (!isMatch && !isHidden);

                        if(action.indexOf("hide") !== -1) isChange = !isChange;
                        //console.log("change", elem, val, action, isMatch, isHidden, isChange)
                    }

                    //console.log("checking", elem, isChange)
                    return isChange;
                }
                $.each(listenersForThisVal, function(index, listener){
                    if(isChanged(listener, value)){
                        $(listener).trigger("modelChange", {value: value, run:run, animate: animate});
                        //console.log("Triggering model change:", listener, value.label, key);
                    }
                });
            });
        });
    }

    var save = function(elem, oldvalue){
        var key = $(elem).data("model") ? $(elem).data("model") : $(elem).attr("name");
        if(key.toLowerCase().indexOf("d_") == 0){//Save in Run API
            F.API.Run.saveValues(elem, populate, null, {onError: function(error, actualError, responseText){
                responseText = $.parseJSON(responseText);
                if(responseText.errors){
                    $.each(responseText.errors, function(index, error){
                        var name = error.name.toLowerCase();
                        var type = error.type.toUpperCase();

                        if(type === "ABOVE_MAX" || type === "BELOW_MIN"){
                            if(error.decisionMax || error.decisionMax === 0)  $(elem).attr("max", error.decisionMax);
                            if(error.decisionMin || error.decisionMin === 0)  $(elem).attr("min", error.decisionMin);

                            $(elem).trigger("change.d", {oldvalue:oldvalue}); //Let the client-side validation kick in
                        }
                        else if(type === "PARSE_ERROR"){

                        }
                    })
                }
            }});
        }
        else{
            //Save in Data API. TODO: I'm saving but not reading from it right now
            F.API.Data.save(elem, populate);
        }
    }

    //Handles on change events of UI elemts
    var uiChangeHandler = {
        base: function(){
            //MakeQueryString has logic for not saving a blank radio button, checkboxes etc, so just pass it through
            save(this);
        },
        radio: function () {
            if (this.checked)   save(this);
        },
        textFocus: function(){
            $(this).data("valid", $(this).val());
        },
        text:  function(evt, data){
            var $elem = $(this);

            var min = $elem.attr("min") || $elem.data("min");
            var max = $elem.attr("max") || $elem.data("max");

            min = parseFloat(min);
            max = parseFloat(max);

            var val = F.Number.extract($elem.val());
            var prevData = (data && data.oldvalue)? data.oldvalue : $elem.data("valid");

            if(isNaN(val)){
                $elem.val(prevData);
                $elem.trigger("validationFailed",
                    {value: val, type: "invalid-entry", text: "Invalid Entry. Please enter a Number."});
            }
            else if(((min || min === 0) && val < parseFloat(min)) || ((max || max === 0) && val > parseFloat(max))){
                var msg, type;
                $elem.val(prevData);

                if((min || min === 0) && val < parseFloat(min)){
                    msg = "Please enter a value greater than " + min;
                    type = "min";
                }
                else{
                    msg = "Please enter a value less than " + max;
                    type = "max";
                }

                $elem.trigger("validationFailed",  {value: val, type: type, text: msg, min: min, max: max});
            }
            else{
                // put the 'clean' value into the element, so the save actually works
                $elem.val(val);
                save(this, prevData);
                $elem.data("valid", $elem.val());
            }
        }
    }

    //Handles model updated events and updates ui elements
    var modelChangeHandler = function(){
            var getVal = function(valsObj){
                var val = (valsObj.decision) ? valsObj.decision: valsObj.result;
                return parseFloat(val);
            }
            var getFormattedVal = function(valsObj){
                return (valsObj.decisionFormatted) ? valsObj.decisionFormatted: valsObj.resultFormatted;
            }
            var matchVals = function(elem, valsObj){
                var actualVal = getVal(valsObj);
                var checkVal, valName;
                try{
                    checkVal = parseFloat($(elem).data("model").split("=")[1]);
                }
                catch (e){
                    throw new Error("To use show/hide the model should be in <modelname=value> format");
                }
                return (actualVal === checkVal);
            }

            var actionHandler = function(elem, type, params){
                var actions = ($(elem).data("action")) || "";
                    actions = actions.split(",");

                if(!F.Array.contains("noop", actions)){
                    actions.push("update");
                }
                //console.log("Actions found", actions, elem);
                //console.log("Triggered", elem, type, params.value.label, actions);
                //TODO: Make this configureable
                var CHANGE_CLASS = "changed";
                var HIDDEN_CLASS = "hidden";

                var defaultActionList = function(){
                    var toggle = function(isShow, elem, params){
                        var elemmodelName = $(elem).data("model").split("=")[0].toLowerCase();
                        var modelName = params.value.label.toLowerCase();

                        if(modelName === elemmodelName){
                            if((isShow && matchVals(elem, params.value)) || (!isShow && !matchVals(elem, params.value))){
                                $(elem).removeClass(HIDDEN_CLASS);
                                //console.log("removing", HIDDEN_CLASS, "from", elem, params)
                            }
                            else{
                                //console.log("adding", HIDDEN_CLASS, "to", elem, params)
                                $(elem).addClass(HIDDEN_CLASS);
                            }
                        }
                    }
                    return{
                        update: function(params){
                            switch(type){
                                case "radio":
                                case "checkbox":
                                    elem.checked = !elem.checked;
                                    break;
                                case "text":
                                    $(elem).val(getFormattedVal(params.value));
                                    break;
                                default:
                                    $(elem).text(getFormattedVal(params.value));
                            }
                            if(params.animate){
                                $(elem).addClass(CHANGE_CLASS);
                                setTimeout(function(){$(elem).removeClass(CHANGE_CLASS)}, 1500)
                            }
                        },
                        show: function(params){
                            return toggle(true, elem, params);
                        },
                        hide:  function(params){
                            return toggle(false, elem, params);
                        },
                        noop: $.noop
                    }
                }()

                $.each(actions, function(index, action){
                    action = $.trim(action);
                    if(action){
                        if(defaultActionList[action]){
                            defaultActionList[action](params);
                        }
                        else{
                            throw new Error("No default action found for " + action);
                        }
                    }

                })
            }
        return{
            radio: function(event, params){
                actionHandler(this, "radio", params)
            },
            text: function(event, params){
                actionHandler(this, "text", params)
            },
            base: function(event, params){
                actionHandler(this, "base", params);
            }
        }
    }()
    return{
        /**
         * Gets values from Run API and populates data for everything on the page with a defined model
         */
        populateUI: populate,
        init: function(selector){
            $(function(){
                selector || (selector = "body");
                selector+= " ";

                $(selector)
                    .off("click.d keyup.d blur.d change.d focus.d modelChange.default", "**")
                    .on("change.d", ":radio:dataModel", uiChangeHandler.radio) //Need change not click because can be triggered by clickin on labels
                    .on("blur.d", "textarea:dataModel", uiChangeHandler.base)
                    .on("change.d", ":checkbox:dataModel", uiChangeHandler.base)
                    .on("change.d", "select:dataModel", uiChangeHandler.base)
                    .on("keyup.d", ":text.autoformat", function(){
                        var format = $(this).data("format");
                        if(!format) return false;

                        var formatted = F.Number.format(F.Number.extract($(this).val()), format);
                        if(formatted !== "?"){
                            $(this).val(formatted);
                        }
                    })
                    .on("change.d", "input[type='range']:dataModel", uiChangeHandler.base)

                    .on("focus.d", ":text:dataModel,input[type='number']:dataModel", uiChangeHandler.textFocus)
                    .on("change.d", ":text:dataModel:not(.autoformat), input[type='number']:dataModel", uiChangeHandler.text)
                    .on("blur.d", ":text:dataModel.autoformat, input[type='number']:dataModel", uiChangeHandler.text)

                    .on("modelChange.default", ":radio:dataModel,:checkbox:dataModel", modelChangeHandler.radio)
                    .on("modelChange.default", "textarea:dataModel, :text:dataModel," +
                            "select:dataModel, input[type='number']:dataModel", modelChangeHandler.text)
                    .on("modelChange.default", ":dataModel:not(input,select,textarea)", modelChangeHandler.base)
            })
        }
    }
}())
<|endoftext|>


<commit_before>/**
 * Module Dependencies
 */

var hasOwnProperty = require('../utils/helpers').object.hasOwnProperty;

/**
 * Collection Loader
 *
 * @param {Object} connections
 * @param {Object} collection
 * @api public
 */

var CollectionLoader = module.exports = function(collection, connections) {

  // Normalize and validate the collection
  this.collection = this._validate(collection, connections);

  // Find the named connections used in the collection
  this.namedConnections = this._getConnections(collection, connections);

  return this;
};

/**
 * Initalize the collection
 *
 * @param {Object} context
 * @param {Function} callback
 * @api public
 */

CollectionLoader.prototype.initialize = function initialize(context) {
  return new this.collection(context, this.namedConnections);
};

/**
 * Validate Collection structure.
 *
 * @param {Object} collection
 * @param {Object} connections
 * @api private
 */

CollectionLoader.prototype._validate = function _validate(collection, connections) {

  // Throw Error if no Tablename/Identity is set
  if(!hasOwnProperty(collection.prototype, 'tableName') && !hasOwnProperty(collection.prototype, 'identity')) {
    throw new Error('A tableName or identity property must be set.');
  }

  // Normalize tableName to identity
  if(hasOwnProperty(collection.prototype, 'tableName')) {
    collection.prototype.identity = collection.prototype.tableName;
  }

  // Ensure identity is lowercased
  collection.prototype.identity = collection.prototype.identity.toLowerCase();

  // Find the connections used by this collection
  // If none is specified check if a default connection exist
  if(!hasOwnProperty(collection.prototype, 'connection')) {

    // Check if a default connection was specified
    if(!hasOwnProperty(connections, 'default')) {
      throw new Error('No adapter was specified for collection: ' + collection.prototype.identity);
    }

    // Set the connection as the default
    collection.prototype.connection = 'default';
  }

  return collection;
};

/**
 * Get the named connections
 *
 * @param {Object} collection
 * @param {Object} connections
 * @api private
 */

CollectionLoader.prototype._getConnections = function _getConnections(collection, connections) {

  // Hold the used connections
  var usedConnections = {};

  // Normalize connection to array
  if(!Array.isArray(collection.prototype.connection)) {
    collection.prototype.connection = [collection.prototype.connection];
  }

  // Set the connections used for the adapter
  collection.prototype.connection.forEach(function(conn) {

    // Ensure the named connection exist
    if(!hasOwnProperty(connections, conn)) {
      var msg = 'The connection ' + conn + ' specified in ' + collection.prototype.identity + ' does not exists.';
      throw new Error(msg);
    }

    usedConnections[conn] = connections[conn];
  });

  return usedConnections;
};
<commit_msg>move identity/tableName normalization to waterline schema<commit_after>/**
 * Module Dependencies
 */

var hasOwnProperty = require('../utils/helpers').object.hasOwnProperty;

/**
 * Collection Loader
 *
 * @param {Object} connections
 * @param {Object} collection
 * @api public
 */

var CollectionLoader = module.exports = function(collection, connections) {

  // Normalize and validate the collection
  this.collection = this._validate(collection, connections);

  // Find the named connections used in the collection
  this.namedConnections = this._getConnections(collection, connections);

  return this;
};

/**
 * Initalize the collection
 *
 * @param {Object} context
 * @param {Function} callback
 * @api public
 */

CollectionLoader.prototype.initialize = function initialize(context) {
  return new this.collection(context, this.namedConnections);
};

/**
 * Validate Collection structure.
 *
 * @param {Object} collection
 * @param {Object} connections
 * @api private
 */

CollectionLoader.prototype._validate = function _validate(collection, connections) {

  // Throw Error if no Tablename/Identity is set
  if(!hasOwnProperty(collection.prototype, 'tableName') && !hasOwnProperty(collection.prototype, 'identity')) {
    throw new Error('A tableName or identity property must be set.');
  }

  // Ensure identity is lowercased
  collection.prototype.identity = collection.prototype.identity.toLowerCase();

  // Find the connections used by this collection
  // If none is specified check if a default connection exist
  if(!hasOwnProperty(collection.prototype, 'connection')) {

    // Check if a default connection was specified
    if(!hasOwnProperty(connections, 'default')) {
      throw new Error('No adapter was specified for collection: ' + collection.prototype.identity);
    }

    // Set the connection as the default
    collection.prototype.connection = 'default';
  }

  return collection;
};

/**
 * Get the named connections
 *
 * @param {Object} collection
 * @param {Object} connections
 * @api private
 */

CollectionLoader.prototype._getConnections = function _getConnections(collection, connections) {

  // Hold the used connections
  var usedConnections = {};

  // Normalize connection to array
  if(!Array.isArray(collection.prototype.connection)) {
    collection.prototype.connection = [collection.prototype.connection];
  }

  // Set the connections used for the adapter
  collection.prototype.connection.forEach(function(conn) {

    // Ensure the named connection exist
    if(!hasOwnProperty(connections, conn)) {
      var msg = 'The connection ' + conn + ' specified in ' + collection.prototype.identity + ' does not exists.';
      throw new Error(msg);
    }

    usedConnections[conn] = connections[conn];
  });

  return usedConnections;
};
<|endoftext|>


<commit_before>require('angular');

var app = angular.module('maneuver-screen', []);

app.directive("maneuver-screen", function() {
  return {
    restrict: 'E',
    templateUrl: "/ng-modules/maneuver-screen/maneuverScreen.html"
  };
});

app.controller('ManeuverScreenCtrl', function($scope){
  $scope.choices = [
    {
      'text': 'Take the high path',
      'response': function(){console.log('Chose "Take the high path"')}
    },
    {
      'text': 'Take the low path',
      'response': function(){console.log('Chose "Take the low path"')}
    }
  ];

  //background image

  //title
}

module.exports = angular.module('maneuver-screen').name;
<commit_msg>there needs to be an easier way to fix naming errors<commit_after>require('angular');

var app = angular.module('maneuver-screen', []);

app.directive("maneuver-screen", function() {
  return {
    restrict: 'E',
    templateUrl: "/ng-modules/maneuverScreen/maneuverScreen.html"
  };
});

app.controller('ManeuverScreenCtrl', function($scope){
  $scope.choices = [
    {
      'text': 'Take the high path',
      'response': function(){console.log('Chose "Take the high path"')}
    },
    {
      'text': 'Take the low path',
      'response': function(){console.log('Chose "Take the low path"')}
    }
  ];

  //background image

  //title
}

module.exports = angular.module('maneuver-screen').name;
<|endoftext|>


<commit_before>'use strict';

var tournamentControllers = angular.module('tournamentControllers', []);

tournamentControllers.controller('TournamentListCtrl', ['$scope', 'Tournament', '$location', function($scope, Tournament, $location) {
  console.log('Tournament list controller');
  $scope.tournaments = Tournament.query();

  $scope.tournaments.$promise.then(function(result){
    if(!$scope.tournaments || ($scope.tournaments && !$scope.tournaments.length))
      $scope.noTournamentsMessage = 'You have no tournaments';
  });

  $scope.searchTournament = function(){
    console.log('TournamentListCtrl: searchTournament');
    console.log('keywords: ', $scope.keywords)
    $location.search('q', $scope.keywords).path('/tournaments/search');
  };

  // experimental: sar
  // start world cup create action
  $scope.createWorldCup = function(){
    console.log('Creating world cup');
    Tournament.saveWorldCup($scope.tournament,
		    function(tournament) {
		      console.log('World Cup Tournament: ', tournament);
		      $location.path('/tournaments/show/' + tournament.Id);
		    },
		    function(err) {
		      console.log('save failed: ', err.data);
		      $scope.messageDanger = err.data;
		    });
  };
  // end world cup create action
}]);

tournamentControllers.controller('TournamentSearchCtrl', ['$scope', '$routeParams', 'Tournament', '$location', function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament search controller');
  console.log('routeParams: ', $routeParams);
  // get tournaments data result from search query
  $scope.tournamentsData = Tournament.search( {q:$routeParams.q});

  $scope.tournamentsData.$promise.then(function(result){
    $scope.tournaments = result.Tournaments;
    $scope.messageInfo = result.MessageInfo;
  });

  $scope.query = $routeParams.q;
  // use the isSearching mode to differientiate:
  // no tournaments in app AND no tournaments found using query search
  $scope.isSearching = true;
  $scope.searchTournament = function(){
    console.log('TournamentSearchCtrl: searchTournament');
    console.log('keywords: ', $scope.keywords)
    $location.search('q', $scope.keywords).path('/tournaments/search');
  };
}]);

tournamentControllers.controller('TournamentNewCtrl', ['$scope', 'Tournament', '$location', function($scope, Tournament, $location) {
  console.log('Tournament New controller');

  $scope.addTournament = function() {
    Tournament.save($scope.tournament,
		    function(tournament) {
		      $location.path('/tournaments/show/' + tournament.Id);
		    },
		    function(err) {
		      console.log('save failed: ', err.data);
		      $scope.messageDanger = err.data;
		    });
  };
}]);

tournamentControllers.controller('TournamentShowCtrl', ['$scope', '$routeParams', 'Tournament', '$location', '$q', function($scope, $routeParams, Tournament, $location, $q) {
  console.log('Tournament Show controller');

  $scope.tournamentData =  Tournament.get({ id:$routeParams.id });
  console.log('tournamentData', $scope.tournamentData);

  // get candidates data from tournament id
  $scope.candidatesData = Tournament.candidates({id:$routeParams.id});

  // do we really need theses lines?
  $scope.candidatesData.$promise.then(function(result){
    $scope.candidates = result.Candidates;
  });

  // #experimental: sar
  // list of tournament groups
  $scope.groupsData = Tournament.groups({id:$routeParams.id});

  $scope.deleteTournament = function() {
    Tournament.delete({ id:$routeParams.id },
		      function(){
			$location.path('/');
		      },
		      function(err) {
			console.log('delete failed: ', err.data);
			$scope.messageDanger = err.data;
		      });
  };

  $scope.joinTournament = function(){
    Tournament.join({ id:$routeParams.id }).$promise.then(function(result){
      Tournament.participants({ id:$routeParams.id }).$promise.then(function(participantsResult){
        $scope.tournamentData.Participants = participantsResult.Participants;
        $scope.joinButtonName = 'Leave';
        $scope.joinButtonMethod = $scope.leaveTournament;
      });
    });
  };

  $scope.leaveTournament = function(){
    Tournament.leave({ id:$routeParams.id }).$promise.then(function(result){
      Tournament.participants({ id:$routeParams.id }).$promise.then(function(participantsResult){
        $scope.tournamentData.Participants = participantsResult.Participants;
        $scope.joinButtonName = 'Join';
        $scope.joinButtonMethod = $scope.joinTournament;
      });
    });
  };

  $scope.joinTournamentAsTeam = function(teamId){
    Tournament.joinAsTeam({id:$routeParams.id, teamId:teamId}).$promise.then(function(result){
      Tournament.get({ id:$routeParams.id }).$promise.then(function(tournamentResult){
        $scope.tournamentData.Teams = tournamentResult.Teams;
        $scope.joinAsTeamButtonName[teamId] = 'Leave';
        $scope.joinAsTeamButtonMethod[teamId] = $scope.leaveTournamentAsTeam;
      });
    });
  };

  $scope.leaveTournamentAsTeam = function(teamId){
    Tournament.leaveAsTeam({id:$routeParams.id, teamId:teamId}).$promise.then(function(result){
      Tournament.get({ id:$routeParams.id }).$promise.then(function(tournamentResult){
        $scope.tournamentData.Teams = tournamentResult.Teams;
        $scope.joinAsTeamButtonName[teamId] = 'Join';
        $scope.joinAsTeamButtonMethod[teamId] = $scope.joinTournamentAsTeam;
      });
    });
  };

  $scope.isTournamentAdmin = $scope.tournamentData.$promise.then(function(result){
    console.log('tournament is admin ready!');
    if(result.Tournament.AdminId == $scope.currentUser.Id){
      return true;
    }else{
      return false;
    }
  });

  // checks if user has joined a tournament
  $scope.joined = $scope.tournamentData.$promise.then(function(result){
    console.log('tournament joined ready!');
    return result.Joined;
  });

  $scope.tournamentData.$promise.then(function(tournamentResult){
    var deferred = $q.defer();
    if (tournamentResult.Joined) {
      deferred.resolve('Leave');
    }
    else {
      deferred.resolve('Join');
    }
    return deferred.promise;
  }).then(function(result){
    $scope.joinButtonName = result;
  });

  $scope.tournamentData.$promise.then(function(tournamentResult){
    var deferred = $q.defer();
    if (tournamentResult.Joined) {
      deferred.resolve($scope.leaveTournament);
    }
    else {
      deferred.resolve($scope.joinTournament);
    }
    return deferred.promise;
  }).then(function(result){
    $scope.joinButtonMethod = result;
  });

  $scope.candidatesData.$promise.then(function(candidatesResult){
    var candidatesLength = 0;
    if(candidatesResult.Candidates){
      candidatesLength = candidatesResult.Candidates.length;
    }
    $scope.joinAsTeamButtonName = new Array(candidatesLength);
    $scope.joinAsTeamButtonMethod = new Array(candidatesLength);

    $scope.tournamentData.$promise.then(function(tournamentResult){
      for (var i=0 ; i<candidatesLength; i++)
      {
        if(IsTeamJoined(candidatesResult.Candidates[i].Team.Id, tournamentResult.Teams))
        {
          $scope.joinAsTeamButtonName[candidatesResult.Candidates[i].Team.Id] = 'Leave';
          $scope.joinAsTeamButtonMethod[candidatesResult.Candidates[i].Team.Id] = $scope.leaveTournamentAsTeam;
        } else {
          $scope.joinAsTeamButtonName[candidatesResult.Candidates[i].Team.Id] = 'Join';
          $scope.joinAsTeamButtonMethod[candidatesResult.Candidates[i].Team.Id] = $scope.joinTournamentAsTeam;
        }
      }
    });
  });

  var IsTeamJoined = function(teamId, teams) {
    if(!teams) {
      return false;
    }
    for (var i=0 ; i<teams.length; i++){
      if(teams[i].Id == teamId){
        return true;
      }
    }
  };
}]);

tournamentControllers.controller('TournamentEditCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.updateTournament = function() {
    var tournamentData = Tournament.get({ id:$routeParams.id });
    Tournament.update({ id:$routeParams.id }, $scope.tournamentData.Tournament,
		      function(){
			$location.path('/tournaments/show/' + $routeParams.id);
		      },
		      function(err) {
			console.log('update failed: ', err.data);
			$scope.messageDanger = err.data;
		      });
  }
}]);

tournamentControllers.controller('TournamentCalendarCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament calendar controller');
  console.log('route params', $routeParams)
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.matchesData = Tournament.calendar({id:$routeParams.id, groupby:$routeParams.groupby});
}]);

// Controller for Admin: update results.
// ToDo: Should only be available if you are admin
tournamentControllers.controller('TournamentSetResultsCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament set results controller');
  console.log('route params', $routeParams)
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.matchesData = Tournament.calendar({id:$routeParams.id, groupby:"phase"});

  $scope.updateResult = function(match, matchindex, dayindex, phaseindex){
      console.log('TournamentSetResultsCtrl: updateResult');
      console.log('match: ', match);
      console.log('match: ', match.IdNumber);
      console.log('match result: ', match.Result1, ,' ', match.Result2);
      console.log('indexes: match, day, phase ', matchindex, dayindex, phaseindex);
      // build result string to send to API
      var result = match.Result1 + ' ' + match.Result2;
      $scope.updatedMatch = Tournament.updateMatchResult({ id:$routeParams.id, matchId:match.IdNumber, result:result});
      // update current match view
      $scope.updatedMatch.$promise.then(function(result){
	  console.log('result: ', result);
	  console.log('matchdata: ', $scope.matchesData);
	  console.log('matchdatamatches: ', $scope.matchesData.Phases[phaseindex].Days[dayindex].Matches[matchindex]);
	  $scope.matchesData.Phases[phaseindex].Days[dayindex].Matches[matchindex] = result;
    });

  };

}]);

tournamentControllers.controller('TournamentFirstStageCtrl',  ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament first stage controller');
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  // #experimental: sar
  // list of tournament groups
  $scope.groupsData = Tournament.groups({id:$routeParams.id});

}]);

tournamentControllers.controller('TournamentSecondStageCtrl',  ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament second stage controller');
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });
  $scope.matchesData = Tournament.matches({id:$routeParams.id, filter:"second"});
}]);
<commit_msg>Fixed Santiago's shit<commit_after>'use strict';

var tournamentControllers = angular.module('tournamentControllers', []);

tournamentControllers.controller('TournamentListCtrl', ['$scope', 'Tournament', '$location', function($scope, Tournament, $location) {
  console.log('Tournament list controller');
  $scope.tournaments = Tournament.query();

  $scope.tournaments.$promise.then(function(result){
    if(!$scope.tournaments || ($scope.tournaments && !$scope.tournaments.length))
      $scope.noTournamentsMessage = 'You have no tournaments';
  });

  $scope.searchTournament = function(){
    console.log('TournamentListCtrl: searchTournament');
    console.log('keywords: ', $scope.keywords)
    $location.search('q', $scope.keywords).path('/tournaments/search');
  };

  // experimental: sar
  // start world cup create action
  $scope.createWorldCup = function(){
    console.log('Creating world cup');
    Tournament.saveWorldCup($scope.tournament,
		    function(tournament) {
		      console.log('World Cup Tournament: ', tournament);
		      $location.path('/tournaments/show/' + tournament.Id);
		    },
		    function(err) {
		      console.log('save failed: ', err.data);
		      $scope.messageDanger = err.data;
		    });
  };
  // end world cup create action
}]);

tournamentControllers.controller('TournamentSearchCtrl', ['$scope', '$routeParams', 'Tournament', '$location', function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament search controller');
  console.log('routeParams: ', $routeParams);
  // get tournaments data result from search query
  $scope.tournamentsData = Tournament.search( {q:$routeParams.q});

  $scope.tournamentsData.$promise.then(function(result){
    $scope.tournaments = result.Tournaments;
    $scope.messageInfo = result.MessageInfo;
  });

  $scope.query = $routeParams.q;
  // use the isSearching mode to differientiate:
  // no tournaments in app AND no tournaments found using query search
  $scope.isSearching = true;
  $scope.searchTournament = function(){
    console.log('TournamentSearchCtrl: searchTournament');
    console.log('keywords: ', $scope.keywords)
    $location.search('q', $scope.keywords).path('/tournaments/search');
  };
}]);

tournamentControllers.controller('TournamentNewCtrl', ['$scope', 'Tournament', '$location', function($scope, Tournament, $location) {
  console.log('Tournament New controller');

  $scope.addTournament = function() {
    Tournament.save($scope.tournament,
		    function(tournament) {
		      $location.path('/tournaments/show/' + tournament.Id);
		    },
		    function(err) {
		      console.log('save failed: ', err.data);
		      $scope.messageDanger = err.data;
		    });
  };
}]);

tournamentControllers.controller('TournamentShowCtrl', ['$scope', '$routeParams', 'Tournament', '$location', '$q', function($scope, $routeParams, Tournament, $location, $q) {
  console.log('Tournament Show controller');

  $scope.tournamentData =  Tournament.get({ id:$routeParams.id });
  console.log('tournamentData', $scope.tournamentData);

  // get candidates data from tournament id
  $scope.candidatesData = Tournament.candidates({id:$routeParams.id});

  // do we really need theses lines?
  $scope.candidatesData.$promise.then(function(result){
    $scope.candidates = result.Candidates;
  });

  // #experimental: sar
  // list of tournament groups
  $scope.groupsData = Tournament.groups({id:$routeParams.id});

  $scope.deleteTournament = function() {
    Tournament.delete({ id:$routeParams.id },
		      function(){
			$location.path('/');
		      },
		      function(err) {
			console.log('delete failed: ', err.data);
			$scope.messageDanger = err.data;
		      });
  };

  $scope.joinTournament = function(){
    Tournament.join({ id:$routeParams.id }).$promise.then(function(result){
      Tournament.participants({ id:$routeParams.id }).$promise.then(function(participantsResult){
        $scope.tournamentData.Participants = participantsResult.Participants;
        $scope.joinButtonName = 'Leave';
        $scope.joinButtonMethod = $scope.leaveTournament;
      });
    });
  };

  $scope.leaveTournament = function(){
    Tournament.leave({ id:$routeParams.id }).$promise.then(function(result){
      Tournament.participants({ id:$routeParams.id }).$promise.then(function(participantsResult){
        $scope.tournamentData.Participants = participantsResult.Participants;
        $scope.joinButtonName = 'Join';
        $scope.joinButtonMethod = $scope.joinTournament;
      });
    });
  };

  $scope.joinTournamentAsTeam = function(teamId){
    Tournament.joinAsTeam({id:$routeParams.id, teamId:teamId}).$promise.then(function(result){
      Tournament.get({ id:$routeParams.id }).$promise.then(function(tournamentResult){
        $scope.tournamentData.Teams = tournamentResult.Teams;
        $scope.joinAsTeamButtonName[teamId] = 'Leave';
        $scope.joinAsTeamButtonMethod[teamId] = $scope.leaveTournamentAsTeam;
      });
    });
  };

  $scope.leaveTournamentAsTeam = function(teamId){
    Tournament.leaveAsTeam({id:$routeParams.id, teamId:teamId}).$promise.then(function(result){
      Tournament.get({ id:$routeParams.id }).$promise.then(function(tournamentResult){
        $scope.tournamentData.Teams = tournamentResult.Teams;
        $scope.joinAsTeamButtonName[teamId] = 'Join';
        $scope.joinAsTeamButtonMethod[teamId] = $scope.joinTournamentAsTeam;
      });
    });
  };

  $scope.isTournamentAdmin = $scope.tournamentData.$promise.then(function(result){
    console.log('tournament is admin ready!');
    if(result.Tournament.AdminId == $scope.currentUser.Id){
      return true;
    }else{
      return false;
    }
  });

  // checks if user has joined a tournament
  $scope.joined = $scope.tournamentData.$promise.then(function(result){
    console.log('tournament joined ready!');
    return result.Joined;
  });

  $scope.tournamentData.$promise.then(function(tournamentResult){
    var deferred = $q.defer();
    if (tournamentResult.Joined) {
      deferred.resolve('Leave');
    }
    else {
      deferred.resolve('Join');
    }
    return deferred.promise;
  }).then(function(result){
    $scope.joinButtonName = result;
  });

  $scope.tournamentData.$promise.then(function(tournamentResult){
    var deferred = $q.defer();
    if (tournamentResult.Joined) {
      deferred.resolve($scope.leaveTournament);
    }
    else {
      deferred.resolve($scope.joinTournament);
    }
    return deferred.promise;
  }).then(function(result){
    $scope.joinButtonMethod = result;
  });

  $scope.candidatesData.$promise.then(function(candidatesResult){
    var candidatesLength = 0;
    if(candidatesResult.Candidates){
      candidatesLength = candidatesResult.Candidates.length;
    }
    $scope.joinAsTeamButtonName = new Array(candidatesLength);
    $scope.joinAsTeamButtonMethod = new Array(candidatesLength);

    $scope.tournamentData.$promise.then(function(tournamentResult){
      for (var i=0 ; i<candidatesLength; i++)
      {
        if(IsTeamJoined(candidatesResult.Candidates[i].Team.Id, tournamentResult.Teams))
        {
          $scope.joinAsTeamButtonName[candidatesResult.Candidates[i].Team.Id] = 'Leave';
          $scope.joinAsTeamButtonMethod[candidatesResult.Candidates[i].Team.Id] = $scope.leaveTournamentAsTeam;
        } else {
          $scope.joinAsTeamButtonName[candidatesResult.Candidates[i].Team.Id] = 'Join';
          $scope.joinAsTeamButtonMethod[candidatesResult.Candidates[i].Team.Id] = $scope.joinTournamentAsTeam;
        }
      }
    });
  });

  var IsTeamJoined = function(teamId, teams) {
    if(!teams) {
      return false;
    }
    for (var i=0 ; i<teams.length; i++){
      if(teams[i].Id == teamId){
        return true;
      }
    }
  };
}]);

tournamentControllers.controller('TournamentEditCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.updateTournament = function() {
    var tournamentData = Tournament.get({ id:$routeParams.id });
    Tournament.update({ id:$routeParams.id }, $scope.tournamentData.Tournament,
		      function(){
			$location.path('/tournaments/show/' + $routeParams.id);
		      },
		      function(err) {
			console.log('update failed: ', err.data);
			$scope.messageDanger = err.data;
		      });
  }
}]);

tournamentControllers.controller('TournamentCalendarCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament calendar controller');
  console.log('route params', $routeParams)
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.matchesData = Tournament.calendar({id:$routeParams.id, groupby:$routeParams.groupby});
}]);

// Controller for Admin: update results.
// ToDo: Should only be available if you are admin
tournamentControllers.controller('TournamentSetResultsCtrl', ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament set results controller');
  console.log('route params', $routeParams)
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  $scope.matchesData = Tournament.calendar({id:$routeParams.id, groupby:"phase"});

  $scope.updateResult = function(match, matchindex, dayindex, phaseindex){
      console.log('TournamentSetResultsCtrl: updateResult');
      console.log('match: ', match);
      console.log('match: ', match.IdNumber);
      console.log('match result: ', match.Result1, ' ', match.Result2);
      console.log('indexes: match, day, phase ', matchindex, dayindex, phaseindex);
      // build result string to send to API
      var result = match.Result1 + ' ' + match.Result2;
      $scope.updatedMatch = Tournament.updateMatchResult({ id:$routeParams.id, matchId:match.IdNumber, result:result});
      // update current match view
      $scope.updatedMatch.$promise.then(function(result){
	  console.log('result: ', result);
	  console.log('matchdata: ', $scope.matchesData);
	  console.log('matchdatamatches: ', $scope.matchesData.Phases[phaseindex].Days[dayindex].Matches[matchindex]);
	  $scope.matchesData.Phases[phaseindex].Days[dayindex].Matches[matchindex] = result;
    });

  };

}]);

tournamentControllers.controller('TournamentFirstStageCtrl',  ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament first stage controller');
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });

  // #experimental: sar
  // list of tournament groups
  $scope.groupsData = Tournament.groups({id:$routeParams.id});

}]);

tournamentControllers.controller('TournamentSecondStageCtrl',  ['$scope', '$routeParams', 'Tournament', '$location',function($scope, $routeParams, Tournament, $location) {
  console.log('Tournament second stage controller');
  $scope.tournamentData = Tournament.get({ id:$routeParams.id });
  $scope.matchesData = Tournament.matches({id:$routeParams.id, filter:"second"});
}]);
<|endoftext|>


<commit_before>var Lang = A.Lang,
    isBoolean = Lang.isBoolean,

    _DOT = '.',
    _SPACE = ' ',

    ACTIVE = 'active',
    BOUNDING_BOX = 'boundingBox',
    CONTENT = 'content',
    LABEL = 'label',
    LIST_NODE = 'listNode',
    NAV = 'nav',
    PANE = 'pane',
    PILLS = 'pills',
    STACKED = 'stacked',
    SYNC_UI = 'syncUI',
    TAB = 'tab',
    TABBABLE = 'tabbable',
    TABS = 'tabs',
    TYPE = 'type',
    TYPE_CHANGE = 'typeChange',

    getClassName = A.getClassName;

A.TabviewBase._classNames = {
    selectedPanel: getClassName(ACTIVE),
    selectedTab: getClassName(ACTIVE),
    tab: getClassName(TAB),
    tabLabel: getClassName(TAB, LABEL),
    tabPanel: getClassName(TAB, PANE),
    tabview: getClassName(TABBABLE),
    tabviewList: getClassName(NAV),
    tabviewListStacked: getClassName(NAV, STACKED),
    tabviewPanel: getClassName(TAB, CONTENT)
};

A.TabviewBase._queries = {
    selectedPanel: '> div ' + _DOT + A.TabviewBase._classNames.selectedPanel,
    selectedTab: '> ul > ' + _DOT + A.TabviewBase._classNames.selectedTab,
    tab: '> ul > li',
    tabLabel: '> ul > li > a',
    tabPanel: '> div > div',
    tabview: _DOT + A.TabviewBase._classNames.tabview,
    tabviewList: '> ul',
    tabviewPanel: '> div'
};

A.TabView.NAME = 'tabbable';
A.TabView.CSS_PREFIX = getClassName('tabbable');
A.Tab.CSS_PREFIX = getClassName('tab');

A.TabView = A.Component.create({
    NAME: TABBABLE,

    CSS_PREFIX: getClassName(TABBABLE),

    ATTRS: {
        stacked: {
            validator: isBoolean,
            value: false
        },

        type: {
            validator: function(val) {
                return val === TABS || val === PILLS;
            },
            value: TABS
        }
    },

    UI_ATTRS: [STACKED, TYPE],

    EXTENDS: A.TabView,

    prototype: {
        LIST_TEMPLATE: '',
        PANEL_TEMPLATE: '',

        initializer: function() {
            var instance = this;

            instance.after(instance._afterSyncUI, instance, SYNC_UI);
            instance.after(TYPE_CHANGE, instance._afterTypeChange);
        },

        _afterSelectionChange: function(event) {
            var instance = this,
                newVal = event.newVal,
                prevVal = event.prevVal,
                selectedTabClassName = A.TabviewBase._classNames.selectedTab;

            A.TabView.superclass._afterSelectionChange.apply(this, arguments);

            if (newVal) {
                newVal.get(BOUNDING_BOX).addClass(selectedTabClassName);
            }
            if (prevVal) {
                prevVal.get(BOUNDING_BOX).removeClass(selectedTabClassName);
            }
        },

        _afterTypeChange: function(event) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            if (event.prevVal) {
                listNode.removeClass(getClassName(NAV, event.prevVal));
            }
        },

        _uiSetType: function(val) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            listNode.addClass(getClassName(NAV, val));
        },

        _uiSetStacked: function(val) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            listNode.toggleClass(
                A.TabviewBase._classNames.tabviewListStacked, val);
        }
    }
});<commit_msg>Add enableTab/disableTab methods to make aui-tabview API backwards compatible<commit_after>var Lang = A.Lang,
    isBoolean = Lang.isBoolean,

    _DOT = '.',
    _SPACE = ' ',

    ACTIVE = 'active',
    BOUNDING_BOX = 'boundingBox',
    CONTENT = 'content',
    DISABLED = 'disabled',
    LABEL = 'label',
    LIST_NODE = 'listNode',
    NAV = 'nav',
    PANE = 'pane',
    PILLS = 'pills',
    STACKED = 'stacked',
    SYNC_UI = 'syncUI',
    TAB = 'tab',
    TABBABLE = 'tabbable',
    TABS = 'tabs',
    TYPE = 'type',
    TYPE_CHANGE = 'typeChange',

    getClassName = A.getClassName;

A.TabviewBase._classNames = {
    selectedPanel: getClassName(ACTIVE),
    selectedTab: getClassName(ACTIVE),
    tab: getClassName(TAB),
    tabLabel: getClassName(TAB, LABEL),
    tabPanel: getClassName(TAB, PANE),
    tabview: getClassName(TABBABLE),
    tabviewList: getClassName(NAV),
    tabviewListStacked: getClassName(NAV, STACKED),
    tabviewPanel: getClassName(TAB, CONTENT)
};

A.TabviewBase._queries = {
    selectedPanel: '> div ' + _DOT + A.TabviewBase._classNames.selectedPanel,
    selectedTab: '> ul > ' + _DOT + A.TabviewBase._classNames.selectedTab,
    tab: '> ul > li',
    tabLabel: '> ul > li > a',
    tabPanel: '> div > div',
    tabview: _DOT + A.TabviewBase._classNames.tabview,
    tabviewList: '> ul',
    tabviewPanel: '> div'
};

A.TabView.NAME = 'tabbable';
A.TabView.CSS_PREFIX = getClassName('tabbable');
A.Tab.CSS_PREFIX = getClassName('tab');

A.TabView = A.Component.create({
    NAME: TABBABLE,

    CSS_PREFIX: getClassName(TABBABLE),

    ATTRS: {
        stacked: {
            validator: isBoolean,
            value: false
        },

        type: {
            validator: function(val) {
                return val === TABS || val === PILLS;
            },
            value: TABS
        }
    },

    UI_ATTRS: [STACKED, TYPE],

    EXTENDS: A.TabView,

    prototype: {
        initializer: function() {
            var instance = this;

            instance.after(instance._afterSyncUI, instance, SYNC_UI);
            instance.after(TYPE_CHANGE, instance._afterTypeChange);
        },

        disableTab: function(i) {
            var instance = this;

            instance.item(i).set(DISABLED, true);
        },

        enableTab: function(i) {
            var instance = this;

            instance.item(i).set(DISABLED, false);
        },

        _afterSelectionChange: function(event) {
            var instance = this,
                newVal = event.newVal,
                prevVal = event.prevVal,
                selectedTabClassName = A.TabviewBase._classNames.selectedTab;

            A.TabView.superclass._afterSelectionChange.apply(this, arguments);

            if (newVal) {
                newVal.get(BOUNDING_BOX).addClass(selectedTabClassName);
            }
            if (prevVal) {
                prevVal.get(BOUNDING_BOX).removeClass(selectedTabClassName);
            }
        },

        _afterTypeChange: function(event) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            if (event.prevVal) {
                listNode.removeClass(getClassName(NAV, event.prevVal));
            }
        },

        _uiSetType: function(val) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            listNode.addClass(getClassName(NAV, val));
        },

        _uiSetStacked: function(val) {
            var instance = this,
                listNode = instance.get(LIST_NODE);

            listNode.toggleClass(
                A.TabviewBase._classNames.tabviewListStacked, val);
        }
    }
});<|endoftext|>


<commit_before>/* eslint max-len: ["error", { "code": 140 }] */
'use strict';

describe('Race conditions that leave audio/video track enabled', function () {
  // The idea here is to place high value on making sure that the "mute" and "video-off" buttons in the video interfaces
  // match with the audioTrack.enabled/videoTrack.enabled, so that users don't get the wrong idea about whether
  // the audio or video feed are on.
  //
  // These tests are various ideas for trying to do things in quick succession
  // or "at the same time". We can add more if we think of them.

  let audioTrack;
  let videoTrack;
  let originalAudioTrack;
  let originalVideoTrack;

  // wrap getUserMedia such that it grabs a copy of audio and video tracks for inspection after it's done
  function wrapGetUserMedia() {
    const chrome$ = helper.padChrome$;
    const oldGetUserMedia = chrome$.window.navigator.mediaDevices.getUserMedia;
    chrome$.window.navigator.mediaDevices.getUserMedia = function (mediaConstraints) {
      return oldGetUserMedia.call(chrome$.window.navigator.mediaDevices, mediaConstraints)
          .then((stream) => {
            audioTrack = stream.getAudioTracks()[0];
            videoTrack = stream.getVideoTracks()[0];
            return stream;
          });
    };
  }

  // See if we can trip up the state by "deactivating" webrtc, clicking mute/video-off,
  // and "activating" webrtc in quick succession
  // As of this writing, "deactivating" will make the buttons disappear pretty quickly, making the "click" ineffectual,
  // regardless, but in case we ever change things around, perhaps this test will catch something.
  function testDeactivateClickActivate(done) {
    const chrome$ = helper.padChrome$;

    function loop(i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$.window.ep_webrtc.deactivate();
      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      chrome$.window.ep_webrtc.activate().then(() => {
        // getUserMedia should give us new audio and video Tracks and disable the old one
        expect(originalAudioTrack).to.not.equal(audioTrack);
        expect(originalAudioTrack.readyState).to.equal('ended');
        expect(audioTrack.readyState).to.equal('live');

        expect(originalVideoTrack).to.not.equal(videoTrack);
        expect(originalVideoTrack.readyState).to.equal('ended');
        expect(videoTrack.readyState).to.equal('live');

        // The mute state should be consistent with icon, wherever they land
        expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
        expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
        if (i > 0) {
          return loop(i - 1);
        } else {
          done();
        }
      });
    }
    loop(10);
  }

  // See if we can trip up the state by clicking mute/video-off,
  // "deactivating" webrtc, and "activating" webrtc in quick succession
  function testClickDeactivateActivate(done) {
    const chrome$ = helper.padChrome$;

    function loop(i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      chrome$.window.ep_webrtc.deactivate();
      chrome$.window.ep_webrtc.activate().then(() => {
        // getUserMedia should give us new audio and video Tracks and disable the old one
        expect(originalAudioTrack).to.not.equal(audioTrack);
        expect(originalAudioTrack.readyState).to.equal('ended');
        expect(audioTrack.readyState).to.equal('live');

        expect(originalVideoTrack).to.not.equal(videoTrack);
        expect(originalVideoTrack.readyState).to.equal('ended');
        expect(videoTrack.readyState).to.equal('live');

        // The mute state should be consistent with icon, wherever they land
        expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
        expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
        if (i > 0) {
          return loop(i - 1);
        } else {
          done();
        }
      });
    }
    loop(10);
  }

  // See if we can trip up the state by "deactivating" webrtc, "activating" webrtc, and then clicking mute/video-off
  // right after the interface returns. As of this writing, addInterface is called twice. we'll try to catch it on
  // the first call.
  function testDeactivateActivateClick(done) {
    const chrome$ = helper.padChrome$;

    function loop(i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$.window.ep_webrtc.deactivate();
      chrome$.window.ep_webrtc.activate();

      helper.waitFor(() => chrome$ && chrome$('.interface-container').length === 1, 2000).done(() => {
        chrome$('.audio-btn').click();
        chrome$('.video-btn').click();

        // Give it a moment to settle.
        setTimeout(() => { // wait to make sure it's settled
          // getUserMedia should give us new audio and video Tracks and disable the old one
          expect(originalAudioTrack).to.not.equal(audioTrack);
          expect(originalAudioTrack.readyState).to.equal('ended');
          expect(audioTrack.readyState).to.equal('live');

          expect(originalVideoTrack).to.not.equal(videoTrack);
          expect(originalVideoTrack.readyState).to.equal('ended');
          expect(videoTrack.readyState).to.equal('live');

          // The mute state should be consistent with icon, wherever they land
          expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
          expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
          if (i > 0) {
            return loop(i - 1);
          } else {
            done();
          }
        }, 200);
      });
    }
    loop(10);
  }

  // See if we can trip up the state by clicking mute/video-off, "deactivating"/"activating" webrtc, as close to at the
  // same time as we can (using Promise.all)
  function testClickWhileReactivate(done) {
    const chrome$ = helper.padChrome$;

    function loop(i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      Promise.all([
        new Promise(((resolve) => {
          chrome$.window.ep_webrtc.deactivate();
          return chrome$.window.ep_webrtc.activate().then(resolve);
        })),
        new Promise(((resolve) => {
          chrome$('.audio-btn').click();
          chrome$('.video-btn').click();
          resolve();
        })),
      ]).then(() => {
        // getUserMedia should give us new audio and video Tracks and disable the old one
        expect(originalAudioTrack).to.not.equal(audioTrack);
        expect(originalAudioTrack.readyState).to.equal('ended');
        expect(audioTrack.readyState).to.equal('live');

        expect(originalVideoTrack).to.not.equal(videoTrack);
        expect(originalVideoTrack.readyState).to.equal('ended');
        expect(videoTrack.readyState).to.equal('live');

        // The mute state should be consistent with icon, wherever they land
        expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
        expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
        if (i > 0) {
          loop(i - 1);
        } else {
          done();
        }
      });
    }
    loop(10);
  }

  // See if we can trip up the state by clicking mute/video-off many times at once.
  // We click mute an odd number of times and video-off an even number of times.
  function testManyClicks(done) {
    const chrome$ = helper.padChrome$;

    function loop(i) {
      expect(chrome$('.audio-btn').hasClass('muted'))
          .to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off'))
          .to.equal(!videoTrack.enabled);
      Promise.all([
        new Promise(((resolve) => { chrome$('.audio-btn').click(); resolve(); })),
        new Promise(((resolve) => { chrome$('.audio-btn').click(); resolve(); })),
        new Promise(((resolve) => { chrome$('.audio-btn').click(); resolve(); })),
        new Promise(((resolve) => { chrome$('.video-btn').click(); resolve(); })),
        new Promise(((resolve) => { chrome$('.video-btn').click(); resolve(); })),
      ]).then(() => {
        setTimeout(() => { // wait to make sure it's settled
          expect(chrome$('.audio-btn').hasClass('muted'))
              .to.equal(!audioTrack.enabled);
          expect(chrome$('.video-btn').hasClass('off'))
              .to.equal(!videoTrack.enabled);
          if (i > 0) {
            loop(i - 1);
          } else {
            done();
          }
        }, 100);
      });
    }

    loop(10);
  }

  context('audio and video enabled on start', function () {
    beforeEach(function (done) {
      audioTrack = null;
      videoTrack = null;

      helper.newPad({
        padPrefs: {
          rtcEnabled: false,
          fakeWebrtcFirefox: true,
          audioEnabledOnStart: true,
          videoEnabledOnStart: true,
        },
        cb() {
          const chrome$ = helper.padChrome$;

          helper.waitFor(() => chrome$ && chrome$('#options-enablertc').length === 1, 2000).done(() => {
            wrapGetUserMedia();

            const $enableRtc = chrome$('#options-enablertc');
            $enableRtc.click(); // Turn it on late so that wrapGetUserMedia works

            helper.waitFor(() => (
              chrome$('.audio-btn').length === 1 &&
                  chrome$('.video-btn').length === 1 &&
                  audioTrack != null &&
                  videoTrack != null
            ), 1000).done(() => {
              // Video interface buttons are added twice,
              // and there's no good way besides a timeout to tell when it's done
              // being called the second time. We want it to be finished so our test is stable.
              setTimeout(done, 200);
            });
          });
        },
      });
      this.timeout(60000);
    });

    it('keeps audio track enabled => mute/video icons consistent when clicking => deactivating => activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      testClickDeactivateActivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when deactivating, then clicking, then activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      testDeactivateClickActivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when deactivating, then activating, then clicking', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      testDeactivateActivateClick(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when clicking while deactivating then activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      testClickWhileReactivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when rapidly clicking mute/video-off', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      testManyClicks(done);
    });
  });

  context('audio and video disabled on start', function () {
    beforeEach(function (done) {
      audioTrack = null;
      videoTrack = null;

      helper.newPad({
        padPrefs: {
          rtcEnabled: false,
          fakeWebrtcFirefox: true,
          audioEnabledOnStart: false,
          videoEnabledOnStart: false,
        },
        cb() {
          const chrome$ = helper.padChrome$;

          helper.waitFor(() => chrome$ && chrome$('#options-enablertc').length === 1, 2000).done(() => {
            wrapGetUserMedia();

            const $enableRtc = chrome$('#options-enablertc');
            $enableRtc.click(); // Turn it on late so that wrapGetUserMedia works

            helper.waitFor(() => (
              chrome$('.audio-btn').length === 1 &&
                  chrome$('.video-btn').length === 1 &&
                  audioTrack != null &&
                  videoTrack != null
            ), 1000).done(() => {
              // Video interface buttons are added twice, and there's no good way besides a timeout to tell when it's done
              // being called the second time. We want it to be finished so our test is stable.
              setTimeout(done, 200);
            });
          });
        },
      });
      this.timeout(60000);
    });

    it('keeps audio track enabled and mute/video icons consistent when clicking, then deactivating, then activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      testClickDeactivateActivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when deactivating, then clicking, then activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      testDeactivateClickActivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when deactivating, then activating, then clicking', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      testDeactivateActivateClick(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when clicking while deactivating then activating', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      testClickWhileReactivate(done);
    });

    it('keeps audio track enabled and mute/video icons consistent when rapidly clicking mute/video-off', function (done) {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      testManyClicks(done);
    });
  });
});
<commit_msg>tests: Promisify `race_conditions.js`<commit_after>'use strict';

describe('Race conditions that leave audio/video track enabled', function () {
  // The idea here is to place high value on making sure that the "mute" and "video-off" buttons in
  // the video interfaces match with the audioTrack.enabled/videoTrack.enabled, so that users don't
  // get the wrong idea about whether the audio or video feed are on.
  //
  // These tests are various ideas for trying to do things in quick succession
  // or "at the same time". We can add more if we think of them.

  let audioTrack;
  let videoTrack;
  let originalAudioTrack;
  let originalVideoTrack;

  // wrap getUserMedia such that it grabs a copy of audio and video tracks for inspection after it's
  // done
  const wrapGetUserMedia = () => {
    const chrome$ = helper.padChrome$;
    const oldGetUserMedia = chrome$.window.navigator.mediaDevices.getUserMedia;
    chrome$.window.navigator.mediaDevices.getUserMedia = async (constraints) => {
      const stream = await oldGetUserMedia.call(chrome$.window.navigator.mediaDevices, constraints);
      audioTrack = stream.getAudioTracks()[0];
      videoTrack = stream.getVideoTracks()[0];
      return stream;
    };
  };

  // See if we can trip up the state by "deactivating" webrtc, clicking mute/video-off, and
  // "activating" webrtc in quick succession. As of this writing, "deactivating" will make the
  // buttons disappear pretty quickly, making the "click" ineffectual, regardless, but in case we
  // ever change things around, perhaps this test will catch something.
  const testDeactivateClickActivate = async () => {
    const chrome$ = helper.padChrome$;

    for (let i = 0; i < 10; ++i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$.window.ep_webrtc.deactivate();
      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      await chrome$.window.ep_webrtc.activate();

      // getUserMedia should give us new audio and video Tracks and disable the old one
      expect(originalAudioTrack).to.not.equal(audioTrack);
      expect(originalAudioTrack.readyState).to.equal('ended');
      expect(audioTrack.readyState).to.equal('live');

      expect(originalVideoTrack).to.not.equal(videoTrack);
      expect(originalVideoTrack.readyState).to.equal('ended');
      expect(videoTrack.readyState).to.equal('live');

      // The mute state should be consistent with icon, wherever they land
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
    }
  };

  // See if we can trip up the state by clicking mute/video-off,
  // "deactivating" webrtc, and "activating" webrtc in quick succession
  const testClickDeactivateActivate = async () => {
    const chrome$ = helper.padChrome$;

    for (let i = 0; i < 10; ++i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      chrome$.window.ep_webrtc.deactivate();
      await chrome$.window.ep_webrtc.activate();

      // getUserMedia should give us new audio and video Tracks and disable the old one
      expect(originalAudioTrack).to.not.equal(audioTrack);
      expect(originalAudioTrack.readyState).to.equal('ended');
      expect(audioTrack.readyState).to.equal('live');

      expect(originalVideoTrack).to.not.equal(videoTrack);
      expect(originalVideoTrack.readyState).to.equal('ended');
      expect(videoTrack.readyState).to.equal('live');

      // The mute state should be consistent with icon, wherever they land
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
    }
  };

  // See if we can trip up the state by "deactivating" webrtc, "activating" webrtc, and then
  // clicking mute/video-off right after the interface returns. As of this writing, addInterface is
  // called twice. we'll try to catch it on the first call.
  const testDeactivateActivateClick = async () => {
    const chrome$ = helper.padChrome$;

    for (let i = 0; i < 10; ++i) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$.window.ep_webrtc.deactivate();
      const p = chrome$.window.ep_webrtc.activate();
      await helper.waitForPromise(
          () => chrome$ && chrome$('.interface-container').length === 1, 2000);
      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();

      // Give it a moment to settle.
      await new Promise((resolve) => setTimeout(resolve, 200));
      await p;

      // getUserMedia should give us new audio and video Tracks and disable the old one
      expect(originalAudioTrack).to.not.equal(audioTrack);
      expect(originalAudioTrack.readyState).to.equal('ended');
      expect(audioTrack.readyState).to.equal('live');

      expect(originalVideoTrack).to.not.equal(videoTrack);
      expect(originalVideoTrack.readyState).to.equal('ended');
      expect(videoTrack.readyState).to.equal('live');

      // The mute state should be consistent with icon, wherever they land
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
    }
  };

  // See if we can trip up the state by clicking mute/video-off, "deactivating"/"activating" webrtc,
  // as close to at the same time as we can
  const testClickWhileReactivate = async () => {
    const chrome$ = helper.padChrome$;

    for (let i = 0; i < 10; i++) {
      originalAudioTrack = audioTrack;
      expect(originalAudioTrack).to.equal(audioTrack);
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);

      originalVideoTrack = videoTrack;
      expect(originalVideoTrack).to.equal(videoTrack);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);

      chrome$.window.ep_webrtc.deactivate();
      const p = chrome$.window.ep_webrtc.activate();
      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      await p;

      // getUserMedia should give us new audio and video Tracks and disable the old one
      expect(originalAudioTrack).to.not.equal(audioTrack);
      expect(originalAudioTrack.readyState).to.equal('ended');
      expect(audioTrack.readyState).to.equal('live');

      expect(originalVideoTrack).to.not.equal(videoTrack);
      expect(originalVideoTrack.readyState).to.equal('ended');
      expect(videoTrack.readyState).to.equal('live');

      // The mute state should be consistent with icon, wherever they land
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
    }
  };

  // See if we can trip up the state by clicking mute/video-off many times at once.
  // We click mute an odd number of times and video-off an even number of times.
  const testManyClicks = async (done) => {
    const chrome$ = helper.padChrome$;

    for (let i = 0; i < 10; ++i) {
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
      chrome$('.audio-btn').click();
      chrome$('.audio-btn').click();
      chrome$('.audio-btn').click();
      chrome$('.video-btn').click();
      chrome$('.video-btn').click();
      await new Promise((resolve) => setTimeout(resolve, 100)); // wait to make sure it's settled
      expect(chrome$('.audio-btn').hasClass('muted')).to.equal(!audioTrack.enabled);
      expect(chrome$('.video-btn').hasClass('off')).to.equal(!videoTrack.enabled);
    }
  };

  describe('audio and video enabled on start', function () {
    beforeEach(async function () {
      this.timeout(60000);
      audioTrack = null;
      videoTrack = null;

      await helper.aNewPad({
        padPrefs: {
          rtcEnabled: false,
          fakeWebrtcFirefox: true,
          audioEnabledOnStart: true,
          videoEnabledOnStart: true,
        },
      });
      const chrome$ = helper.padChrome$;

      await helper.waitForPromise(
          () => chrome$ && chrome$('#options-enablertc').length === 1, 2000);
      wrapGetUserMedia();

      const $enableRtc = chrome$('#options-enablertc');
      $enableRtc.click(); // Turn it on late so that wrapGetUserMedia works

      await helper.waitForPromise(
          () => (chrome$('.audio-btn').length === 1 && chrome$('.video-btn').length === 1 &&
                 audioTrack != null && videoTrack != null),
          1000);
      // Video interface buttons are added twice, and there's no good way besides a timeout to tell
      // when it's done being called the second time. We want it to be finished so our test is
      // stable.
      await new Promise((resolve) => setTimeout(resolve, 200));
    });

    it('click, deactivate, activate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      await testClickDeactivateActivate();
    });

    it('deactivate, click, activate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      await testDeactivateClickActivate();
    });

    it('deactivate, activate, click', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      await testDeactivateActivateClick();
    });

    it('click while reactivate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      await testClickWhileReactivate();
    });

    it('many clicks', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(true);
      expect(videoTrack.enabled).to.equal(true);
      await testManyClicks();
    });
  });

  describe('audio and video disabled on start', function () {
    beforeEach(async function () {
      this.timeout(60000);
      audioTrack = null;
      videoTrack = null;

      await helper.aNewPad({
        padPrefs: {
          rtcEnabled: false,
          fakeWebrtcFirefox: true,
          audioEnabledOnStart: false,
          videoEnabledOnStart: false,
        },
      });
      const chrome$ = helper.padChrome$;

      await helper.waitForPromise(
          () => chrome$ && chrome$('#options-enablertc').length === 1, 2000);
      wrapGetUserMedia();

      const $enableRtc = chrome$('#options-enablertc');
      $enableRtc.click(); // Turn it on late so that wrapGetUserMedia works

      await helper.waitForPromise(
          () => (chrome$('.audio-btn').length === 1 && chrome$('.video-btn').length === 1 &&
                 audioTrack != null && videoTrack != null),
          1000);
      // Video interface buttons are added twice, and there's no good way besides a timeout to tell
      // when it's done being called the second time. We want it to be finished so our test is
      // stable.
      await new Promise((resolve) => setTimeout(resolve, 200));
    });

    it('click, deactivate, activate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      await testClickDeactivateActivate();
    });

    it('deactivate, click, activate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      await testDeactivateClickActivate();
    });

    it('deactivate, activate, click', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      await testDeactivateActivateClick();
    });

    it('click while reactivate', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      await testClickWhileReactivate();
    });

    it('many clicks', async function () {
      this.timeout(5000);
      expect(audioTrack.enabled).to.equal(false);
      expect(videoTrack.enabled).to.equal(false);
      await testManyClicks();
    });
  });
});
<|endoftext|>