<commit_before>var normalizeUrl = require('normalize-url');
var srcByModuleId = Object.create(null);

var noDocument = typeof document === 'undefined';
var forEach = Array.prototype.forEach;

var noop = function () {}

var getCurrentScriptUrl = function(moduleId) {
  var src = srcByModuleId[moduleId];

  if (!src) {
    if (document.currentScript) {
      src = document.currentScript.src;
    } else {
      var scripts = document.getElementsByTagName('script');
      var lastScriptTag = scripts[scripts.length - 1];

      if (lastScriptTag) {
        src = lastScriptTag.src;
      }
    }
    srcByModuleId[moduleId] = src;
  }

  return function(fileMap) {
    var splitResult = /([^\\/]+)\.js$/.exec(src);
    var filename = splitResult && splitResult[1];
    if (!filename) {
      return [src.replace('.js', '.css')];
    }
    return fileMap.split(',').map(function(mapRule) {
      var reg = new RegExp(filename + '\\.js$', 'g')
      return normalizeUrl(src.replace(reg, mapRule.replace(/{fileName}/g, filename) + '.css'), { stripWWW: false });
    });
  };
}

function updateCss(el, url) {
  if (!url) {
    url = el.href.split('?')[0];
  }

  if (!url || !(url.indexOf('.css') > -1)) return;

  var newEl = el.cloneNode();
  newEl.addEventListener('load', function () {
    el.remove();
  });
  newEl.addEventListener('error', function () {
    el.remove();
  });
  newEl.href = url + '?' + Date.now();
  el.parentNode.appendChild(newEl);
}

function reloadStyle(src) {
  var elements = document.querySelectorAll('link');
  var loaded = false;

  forEach.call(elements, function(el) {
    var url = getReloadUrl(el.href, src);
    if (url) {
      updateCss(el, url);
      loaded = true;
    }
  });

  return loaded;
}

function getReloadUrl(href, src) {
  href = normalizeUrl(href, { stripWWW: false });
  var ret;
  src.some(function(url) {
    if (href.indexOf(src) > -1) {
      ret = url;
    }
  });
  return ret;
}

function reloadAll() {
  var elements = document.querySelectorAll('link');
  forEach.call(elements, function(el) {
    updateCss(el);
  });
}

module.exports = function(moduleId, options) {
  var getScriptSrc;

  if (noDocument) {
    return noop;
  }

  getScriptSrc = getCurrentScriptUrl(moduleId);
  return function() {
    var src = getScriptSrc(options.fileMap);
    var reloaded = reloadStyle(src);
    if (reloaded) {
      console.log('[HMR] css reload %s', src.join(' '));
    } else {
      console.log('[HMR] Reload all css');
      reloadAll();
    }
  }
};
<commit_msg>Fix issue causing multiple instances of link tags to be inserted.<commit_after>var normalizeUrl = require('normalize-url');
var srcByModuleId = Object.create(null);

var noDocument = typeof document === 'undefined';
var forEach = Array.prototype.forEach;

var noop = function () {}

var getCurrentScriptUrl = function(moduleId) {
  var src = srcByModuleId[moduleId];

  if (!src) {
    if (document.currentScript) {
      src = document.currentScript.src;
    } else {
      var scripts = document.getElementsByTagName('script');
      var lastScriptTag = scripts[scripts.length - 1];

      if (lastScriptTag) {
        src = lastScriptTag.src;
      }
    }
    srcByModuleId[moduleId] = src;
  }

  return function(fileMap) {
    var splitResult = /([^\\/]+)\.js$/.exec(src);
    var filename = splitResult && splitResult[1];
    if (!filename) {
      return [src.replace('.js', '.css')];
    }
    return fileMap.split(',').map(function(mapRule) {
      var reg = new RegExp(filename + '\\.js$', 'g')
      return normalizeUrl(src.replace(reg, mapRule.replace(/{fileName}/g, filename) + '.css'), { stripWWW: false });
    });
  };
}

function updateCss(el, url) {
  if (!url) {
    url = el.href.split('?')[0];
  }
  if (el.isLoaded === false && url == el.href.split('?')[0]) {
    // We seem to be about to replace a css link that hasn't loaded yet.
    // We're probably changing the same file more than once.
    return;
  }
  if (!url || !(url.indexOf('.css') > -1)) return;

  el.visited = true;
  var newEl = el.cloneNode();

  newEl.isLoaded = false;
  newEl.addEventListener('load', function () {
    newEl.isLoaded = true;
    el.remove();
  });
  newEl.addEventListener('error', function () {
    newEl.isLoaded = true;
    el.remove();
  });

  newEl.href = url + '?' + Date.now();
  el.parentNode.appendChild(newEl);
}

function reloadStyle(src) {
  var elements = document.querySelectorAll('link');
  var loaded = false;

  forEach.call(elements, function(el) {
    if (el.visited === true) return;

    var url = getReloadUrl(el.href, src);
    if (url) {
      updateCss(el, url);
      loaded = true;
    }
  });

  return loaded;
}

function getReloadUrl(href, src) {
  href = normalizeUrl(href, { stripWWW: false });
  var ret;
  src.some(function(url) {
    if (href.indexOf(src) > -1) {
      ret = url;
    }
  });
  return ret;
}

function reloadAll() {
  var elements = document.querySelectorAll('link');
  forEach.call(elements, function(el) {
    if (el.visited === true) return;
    updateCss(el);
  });
}

module.exports = function(moduleId, options) {
  var getScriptSrc;

  if (noDocument) {
    return noop;
  }

  getScriptSrc = getCurrentScriptUrl(moduleId);
  return function() {
    var src = getScriptSrc(options.fileMap);
    var reloaded = reloadStyle(src);
    if (reloaded) {
      console.log('[HMR] css reload %s', src.join(' '));
    } else {
      console.log('[HMR] Reload all css');
      reloadAll();
    }
  }
};
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/domplate",
    "firebug/lib/locale",
    "firebug/chrome/reps",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/array",
],
function(Domplate, Locale, FirebugReps, Dom, Css, Arr) {

// ************************************************************************************************
// Constants

// ************************************************************************************************

with (Domplate) {
FirebugReps.Table = domplate(Firebug.Rep,
{
    className: "table",

    tag:
        DIV({"class": "dataTableSizer", "tabindex": "-1" },
            TABLE({"class": "dataTable", cellspacing: 0, cellpadding: 0, width: "100%",
                "role": "grid"},
                THEAD({"class": "dataTableThead", "role": "presentation"},
                    TR({"class": "headerRow focusRow dataTableRow subFocusRow", "role": "row",
                        onclick: "$onClickHeader"},
                        FOR("column", "$object.columns",
                            TH({"class": "headerCell a11yFocus", "role": "columnheader",
                                $alphaValue: "$column.alphaValue"},
                                DIV({"class": "headerCellBox"},
                                    "$column.label"
                                )
                            )
                        )
                    )
                ),
                TBODY({"class": "dataTableTbody", "role": "presentation"},
                    FOR("row", "$object.data|getRows",
                        TR({"class": "focusRow dataTableRow subFocusRow", "role": "row"},
                            FOR("column", "$row|getColumns",
                                TD({"class": "a11yFocus dataTableCell", "role": "gridcell"},
                                    TAG("$column|getValueTag", {object: "$column"})
                                )
                            )
                        )
                    )
                )
            )
        ),

    getValueTag: function(object)
    {
        var rep = Firebug.getRep(object);
        return rep.shortTag || rep.tag;
    },

    getRows: function(data)
    {
        var props = this.getProps(data);
        if (!props.length)
            return [];
        return props;
    },

    getColumns: function(row)
    {
        if (typeof(row) != "object")
            return [row];

        var cols = [];
        for (var i=0; i<this.columns.length; i++)
        {
            var prop = this.columns[i].property;

            // Object property is not set for this column, so display entire
            // row-value in the cell. This can happen in cases where a generic
            // object is logged using table layout. In such case there is one
            // column (no property associated) and each row represents a member
            // of the object.
            if (typeof prop == "undefined")
            {
                value = row;
            }
            else if (typeof row[prop] == "undefined")
            {
                var props = (typeof(prop) == "string") ? prop.split(".") : [prop];

                var value = row;
                for (var p in props)
                    value = (value && value[props[p]]) || undefined;
            }
            else
            {
                value = row[prop];
            }

            cols.push(value);
        }
        return cols;
    },

    getProps: function(obj)
    {
        if (typeof(obj) != "object")
            return [obj];

        if (FBTrace.DBG_CONSOLE)
            FBTrace.sysout("FirebugReps.Table.getProps", {obj: obj, arr: Arr.cloneArray(obj)});

        if (obj.length)
            return Arr.cloneArray(obj);

        var arr = [];
        for (var p in obj)
        {
            var value = obj[p];
            if (this.domFilter(value, p))
                arr.push(value);
        }

        return arr;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // Sorting

    onClickHeader: function(event)
    {
        var table = Dom.getAncestorByClass(event.target, "dataTable");
        var header = Dom.getAncestorByClass(event.target, "headerCell");
        if (!header)
            return;

        var numerical = !Css.hasClass(header, "alphaValue");

        var colIndex = 0;
        for (header = header.previousSibling; header; header = header.previousSibling)
            ++colIndex;

        this.sort(table, colIndex, numerical);
    },

    sort: function(table, colIndex, numerical)
    {
        var tbody = Dom.getChildByClass(table, "dataTableTbody");
        var thead = Dom.getChildByClass(table, "dataTableThead");

        var values = [];
        for (var row = tbody.childNodes[0]; row; row = row.nextSibling)
        {
            var cell = row.childNodes[colIndex];
            var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
            values.push({row: row, value: value});
        }

        values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });

        var headerRow = thead.firstChild;
        var headerSorted = Dom.getChildByClass(headerRow, "headerSorted");
        Css.removeClass(headerSorted, "headerSorted");
        if (headerSorted)
            headerSorted.removeAttribute('aria-sort');

        var header = headerRow.childNodes[colIndex];
        Css.setClass(header, "headerSorted");

        if (!header.sorted || header.sorted == 1)
        {
            Css.removeClass(header, "sortedDescending");
            Css.setClass(header, "sortedAscending");
            header.setAttribute("aria-sort", "ascending");

            header.sorted = -1;

            for (var i = 0; i < values.length; ++i)
                tbody.appendChild(values[i].row);
        }
        else
        {
            Css.removeClass(header, "sortedAscending");
            Css.setClass(header, "sortedDescending");
            header.setAttribute("aria-sort", "descending")

            header.sorted = 1;

            for (var i = values.length-1; i >= 0; --i)
                tbody.appendChild(values[i].row);
        }
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // Console logging

    log: function(data, cols, context)
    {
        // No arguments passed into console.table method, bail out for now,
        // but some error message could be displayed in the future.
        if (!data)
            return;

        // Get header info from passed argument (can be null).
        var columns = [];
        for (var i=0; cols && i<cols.length; i++)
        {
            var col = cols[i];
            var prop = (typeof(col.property) != "undefined") ? col.property : col;
            var label = (typeof(col.label) != "undefined") ? col.label : prop;

            columns.push({
                property: prop,
                label: label,
                alphaValue: true
            });
        }

        if (FBTrace.DBG_CONSOLE)
            FBTrace.sysout("consoleInjector.table; columns", columns);

        // Generate header info from the data dynamically.
        if (!columns.length)
            columns = this.getHeaderColumns(data);

        // Don't limit strings in the table. It should be mostly ok. In case of
        // complaints we need an option.
        var prevValue = Firebug.stringCropLength;
        Firebug.stringCropLength = -1;

        try
        {
            this.columns = columns;
            var row = Firebug.Console.log({data: data, columns: columns}, context, "table", this, true);

            // Set vertical height for scroll bar.
            var tBody = row.querySelector(".dataTableTbody");
            var maxHeight = Firebug.tabularLogMaxHeight;
            if (maxHeight > 0 && tBody.clientHeight > maxHeight)
                tBody.style.height = maxHeight + "px";
        }
        catch (err)
        {
            if (FBTrace.DBG_CONSOLE)
                FBTrace.sysout("consoleInjector.table; EXCEPTION " + err, err);
        }
        finally
        {
            Firebug.stringCropLength = prevValue;
            delete this.columns;
        }
    },

    /**
     * Analyse data and return dynamically created list of columns.
     * @param {Object} data
     */
    getHeaderColumns: function(data)
    {
        // Get the first row in the object.
        var firstRow;
        for (var p in data)
        {
            firstRow = data[p];
            break;
        }

        if (typeof(firstRow) != "object")
            return [{label: Locale.$STR("firebug.reps.table.ObjectProperties")}];

        // Put together a column property, label and type (type for default sorting logic).
        var header = [];
        for (var p in firstRow)
        {
            var value = firstRow[p];
            if (!this.domFilter(value, p))
                continue;

            header.push({
                property: p,
                label: p,
                alphaValue: (typeof(value) != "number")
            });
        }

        return header;
    },

    /**
     * Filtering based on options set in the DOM panel.
     * @param {Object} value - a property value under inspection.
     * @param {String} name - name of the property.
     * @returns true if the value should be displayed, otherwise false.
     */
    domFilter: function(object, name)
    {
        var domMembers = Dom.getDOMMembers(object, name);

        if (typeof(object) == "function")
        {
            if (Dom.isDOMMember(object, name) && !Firebug.showDOMFuncs)
                return false;
            else if (!Firebug.showUserFuncs)
                return false;
        }
        else
        {
            if (Dom.isDOMMember(object, name) && !Firebug.showDOMProps)
                return false;
            else if (Dom.isDOMConstant(object, name) && !Firebug.showDOMConstants)
                return false;
            else if (!Firebug.showUserProps)
                return false;
        }

        return true;
    }
})};

// ************************************************************************************************
// Registration

return FirebugReps.Table;

// ************************************************************************************************
});
<commit_msg>[1.10] introduce computeColumns so it's easier to use TableRep from extensions.<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/domplate",
    "firebug/lib/locale",
    "firebug/chrome/reps",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/array",
],
function(Domplate, Locale, FirebugReps, Dom, Css, Arr) {

// ********************************************************************************************* //
// Constants

// ********************************************************************************************* //

with (Domplate) {
FirebugReps.Table = domplate(Firebug.Rep,
{
    className: "table",

    tag:
        DIV({"class": "dataTableSizer", "tabindex": "-1" },
            TABLE({"class": "dataTable", cellspacing: 0, cellpadding: 0, width: "100%",
                "role": "grid"},
                THEAD({"class": "dataTableThead", "role": "presentation"},
                    TR({"class": "headerRow focusRow dataTableRow subFocusRow", "role": "row",
                        onclick: "$onClickHeader"},
                        FOR("column", "$object.columns",
                            TH({"class": "headerCell a11yFocus", "role": "columnheader",
                                $alphaValue: "$column.alphaValue"},
                                DIV({"class": "headerCellBox"},
                                    "$column.label"
                                )
                            )
                        )
                    )
                ),
                TBODY({"class": "dataTableTbody", "role": "presentation"},
                    FOR("row", "$object.data|getRows",
                        TR({"class": "focusRow dataTableRow subFocusRow", "role": "row"},
                            FOR("column", "$row|getColumns",
                                TD({"class": "a11yFocus dataTableCell", "role": "gridcell"},
                                    TAG("$column|getValueTag", {object: "$column"})
                                )
                            )
                        )
                    )
                )
            )
        ),

    getValueTag: function(object)
    {
        var rep = Firebug.getRep(object);
        return rep.shortTag || rep.tag;
    },

    getRows: function(data)
    {
        var props = this.getProps(data);
        if (!props.length)
            return [];
        return props;
    },

    getColumns: function(row)
    {
        if (typeof(row) != "object")
            return [row];

        var cols = [];
        for (var i=0; i<this.columns.length; i++)
        {
            var prop = this.columns[i].property;

            // Object property is not set for this column, so display entire
            // row-value in the cell. This can happen in cases where a generic
            // object is logged using table layout. In such case there is one
            // column (no property associated) and each row represents a member
            // of the object.
            if (typeof prop == "undefined")
            {
                value = row;
            }
            else if (typeof row[prop] == "undefined")
            {
                var props = (typeof(prop) == "string") ? prop.split(".") : [prop];

                var value = row;
                for (var p in props)
                    value = (value && value[props[p]]) || undefined;
            }
            else
            {
                value = row[prop];
            }

            cols.push(value);
        }
        return cols;
    },

    getProps: function(obj)
    {
        if (typeof(obj) != "object")
            return [obj];

        if (FBTrace.DBG_CONSOLE)
            FBTrace.sysout("FirebugReps.Table.getProps", {obj: obj, arr: Arr.cloneArray(obj)});

        if (obj.length)
            return Arr.cloneArray(obj);

        var arr = [];
        for (var p in obj)
        {
            var value = obj[p];
            if (this.domFilter(value, p))
                arr.push(value);
        }

        return arr;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Sorting

    onClickHeader: function(event)
    {
        var table = Dom.getAncestorByClass(event.target, "dataTable");
        var header = Dom.getAncestorByClass(event.target, "headerCell");
        if (!header)
            return;

        var numerical = !Css.hasClass(header, "alphaValue");

        var colIndex = 0;
        for (header = header.previousSibling; header; header = header.previousSibling)
            ++colIndex;

        this.sort(table, colIndex, numerical);
    },

    sort: function(table, colIndex, numerical)
    {
        var tbody = Dom.getChildByClass(table, "dataTableTbody");
        var thead = Dom.getChildByClass(table, "dataTableThead");

        var values = [];
        for (var row = tbody.childNodes[0]; row; row = row.nextSibling)
        {
            var cell = row.childNodes[colIndex];
            var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
            values.push({row: row, value: value});
        }

        values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });

        var headerRow = thead.firstChild;
        var headerSorted = Dom.getChildByClass(headerRow, "headerSorted");
        Css.removeClass(headerSorted, "headerSorted");
        if (headerSorted)
            headerSorted.removeAttribute('aria-sort');

        var header = headerRow.childNodes[colIndex];
        Css.setClass(header, "headerSorted");

        if (!header.sorted || header.sorted == 1)
        {
            Css.removeClass(header, "sortedDescending");
            Css.setClass(header, "sortedAscending");
            header.setAttribute("aria-sort", "ascending");

            header.sorted = -1;

            for (var i = 0; i < values.length; ++i)
                tbody.appendChild(values[i].row);
        }
        else
        {
            Css.removeClass(header, "sortedAscending");
            Css.setClass(header, "sortedDescending");
            header.setAttribute("aria-sort", "descending")

            header.sorted = 1;

            for (var i = values.length-1; i >= 0; --i)
                tbody.appendChild(values[i].row);
        }
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Console logging

    log: function(data, cols, context)
    {
        // No arguments passed into console.table method, bail out for now,
        // but some error message could be displayed in the future.
        if (!data)
            return;

        var columns = this.computeColumns(data, cols);

        // Don't limit strings in the table. It should be mostly ok. In case of
        // complaints we need an option.
        var prevValue = Firebug.stringCropLength;
        Firebug.stringCropLength = -1;

        try
        {
            this.columns = columns;
            var row = Firebug.Console.log({data: data, columns: columns}, context, "table", this, true);

            // Set vertical height for scroll bar.
            var tBody = row.querySelector(".dataTableTbody");
            var maxHeight = Firebug.tabularLogMaxHeight;
            if (maxHeight > 0 && tBody.clientHeight > maxHeight)
                tBody.style.height = maxHeight + "px";
        }
        catch (err)
        {
            if (FBTrace.DBG_CONSOLE)
                FBTrace.sysout("consoleInjector.table; EXCEPTION " + err, err);
        }
        finally
        {
            Firebug.stringCropLength = prevValue;
            delete this.columns;
        }
    },

    computeColumns: function(data, cols)
    {
        // Get header info from passed argument (can be null).
        var columns = [];
        for (var i=0; cols && i<cols.length; i++)
        {
            var col = cols[i];
            var prop = (typeof(col.property) != "undefined") ? col.property : col;
            var label = (typeof(col.label) != "undefined") ? col.label : prop;

            columns.push({
                property: prop,
                label: label,
                alphaValue: true
            });
        }

        if (FBTrace.DBG_CONSOLE)
            FBTrace.sysout("consoleInjector.table; columns", columns);

        // Generate header info from the data dynamically.
        if (!columns.length)
            columns = this.getHeaderColumns(data);

        return columns;
    },

    /**
     * Analyse data and return dynamically created list of columns.
     * @param {Object} data
     */
    getHeaderColumns: function(data)
    {
        // Get the first row in the object.
        var firstRow;
        for (var p in data)
        {
            firstRow = data[p];
            break;
        }

        if (typeof(firstRow) != "object")
            return [{label: Locale.$STR("firebug.reps.table.ObjectProperties")}];

        // Put together a column property, label and type (type for default sorting logic).
        var header = [];
        for (var p in firstRow)
        {
            var value = firstRow[p];
            if (!this.domFilter(value, p))
                continue;

            header.push({
                property: p,
                label: p,
                alphaValue: (typeof(value) != "number")
            });
        }

        return header;
    },

    /**
     * Filtering based on options set in the DOM panel.
     * @param {Object} value - a property value under inspection.
     * @param {String} name - name of the property.
     * @returns true if the value should be displayed, otherwise false.
     */
    domFilter: function(object, name)
    {
        var domMembers = Dom.getDOMMembers(object, name);

        if (typeof(object) == "function")
        {
            if (Dom.isDOMMember(object, name) && !Firebug.showDOMFuncs)
                return false;
            else if (!Firebug.showUserFuncs)
                return false;
        }
        else
        {
            if (Dom.isDOMMember(object, name) && !Firebug.showDOMProps)
                return false;
            else if (Dom.isDOMConstant(object, name) && !Firebug.showDOMConstants)
                return false;
            else if (!Firebug.showUserProps)
                return false;
        }

        return true;
    }
})};

// ********************************************************************************************* //
// Registration

return FirebugReps.Table;

// ********************************************************************************************* //
});
<|endoftext|>


<commit_before>var _, ErrorModels, handleError, mongoose, Rat, Rescue, rescue, save, winston

_ = require( 'underscore' )
mongoose = require( 'mongoose' )
winston = require( 'winston' )

Rat = require( '../models/rat' )
Rescue = require( '../models/rescue' )
ErrorModels = require( '../errors' )

// SHARED FUNCTIONS
// =============================================================================
handleError = function ( error ) {
  errorTypes = Object.keys( error.errors )

  console.error( errorTypes )

  for ( var i = 0; i < errorTypes.length; i++ ) {
    var errorModel, errorType

    errorType = errorTypes[i]
    error = error.errors[errorType].properties

    if ( error.type === 'required' ) {
      errorModel = ErrorModels['missing_required_field']
    }

    errorModel.detail = 'You\'re missing the required field: ' + error.path

    response.model.errors.push( errorModel )
  }
}

// GET
// =============================================================================
exports.get = function ( request, response, next ) {

  exports.read( request.body ).then( function( res ) {
    var data = res.data
    var meta = res.meta

    response.model.data = data
    response.model.meta = meta
    response.status = 400
    next()
  }, function( error ) {
    response.model.errors.push( error.error )
    response.status( 400 )
    next()
  })
}

// GET (by ID)
// =============================================================================
exports.getById = function ( request, response, next ) {
  var id

  response.model.meta.params = _.extend( response.model.meta.params, request.params )
  console.log( response.model.meta.params )

  id = request.params.id

  Rescue
  .findById( id )
  .populate( 'rats' )
  .exec( function ( error, rescue ) {
    var status

    if ( error ) {
      response.model.errors.push( error )
      response.status( 400 )

    } else {
      response.model.data = rescue
      response.status( 200 )
    }

    next()
  })
}

exports.read = function ( query ) {
  return new Promise(function (resolve, reject) {
    var filter

    filter = {}
    dbQuery = {}

    filter.size = parseInt( query.limit ) || 25
    delete query.limit

    filter.from = parseInt( query.offset ) || 0
    delete query.offset

    for ( var key in query ) {
      if ( key === 'q' ) {
        dbQuery.query_string = {
          query: request.body.q
        }
      } else {
        if ( !dbQuery.bool ) {
          dbQuery.bool = {
            should: []
          }
        }

        term = {}
        term[key] = {
          query: query[key],
          fuzziness: 'auto'
        }
        dbQuery.bool.should.push( { match: term } )
      }
    }

    if ( !Object.keys( dbQuery ).length ) {
      dbQuery.match_all = {}
    }

    Rescue.search( dbQuery, filter, function ( error, queryData ) {
      if ( error ) {
        var errorObj = ErrorModels.server_error
        errorObj.detail = error
        reject( { error: errorObj, meta: {} } )

      } else {
        var meta = {
          count: queryData.hits.hits.length,
          limit: filter.size,
          offset: filter.from,
          total: queryData.hits.total
        }

        var data = []

        queryData.hits.hits.forEach( function ( rescue, index, rescues ) {
          var rescueToPopulate, rescueFind

          rescue._source._id = rescue._id
          rescue._source.score = rescue._score

          data.push( rescue._source )
        })

        resolve( { data: data, meta: meta } )
      }
    })
  })
}

// POST
// =============================================================================
exports.post = function ( request, response, next ) {
  exports.create( request.body ).then(function( res ) {
    console.log('done')
    response.model.data = res.data
    response.status( 201 )
    next()
  }, function( error ) {
    console.log('erroring')
    response.model.errors.push( error )
    response.status( 400 )
    next()
  })
}

exports.create = function( query ) {
  return new Promise(function(resolve, reject) {
    console.log('0')
    var finds, firstLimpetFind

    finds = []

    console.log(query)
    // Validate and update rats
    if ( typeof query.rats === 'string' ) {
      query.rats = query.rats.split( ',' )
    }

    query.unidentifiedRats = []

    if ( query.rats ) {
      query.rats.forEach( function ( rat, index, rats ) {
        var find, CMDRname

        if ( typeof rat === 'string' ) {
          if ( !mongoose.Types.ObjectId.isValid( rat ) ) {
            CMDRname = rat.trim()

            query.rats = _.without( query.rats, CMDRname )

            find = Rat.findOne({
              CMDRname: CMDRname
            })

            find.then( function ( rat ) {
              if ( rat ) {
                query.rats.push( rat._id )
              } else {
                query.unidentifiedRats.push( CMDRname )
              }
            })

            finds.push( find )
          }

        } else if ( typeof rat === 'object' && rat._id ) {
          query.rats.push( rat._id )
        }
      })
    }

    // Validate and update firstLimpet
    if ( query.firstLimpet ) {
      if ( typeof query.firstLimpet === 'string' ) {
        if ( !mongoose.Types.ObjectId.isValid( query.firstLimpet ) ) {
          firstLimpetFind = Rat.findOne({
            CMDRname: query.firstLimpet.trim()
          })

          firstLimpetFind.then( function ( rat ) {
            if ( rat ) {
              query.firstLimpet = rat._id
            }
          })

          finds.push( firstLimpetFind )
        }

      } else if ( typeof query.firstLimpet === 'object' && query.firstLimpet._id ) {
        query.firstLimpet = query.firstLimpet._id
      }
    }
    Promise.all( finds )
    .then( function () {

      Rescue.create( query, function ( error, rescue ) {
        var errors

        if ( error ) {
          var errorObj = ErrorModels.server_error
          errorObj.detail = error
          reject( { error: errorObj, meta: {} } )

        } else {
          resolve ( { data: rescue, meta: {} } )
        }
      })
    })
  })
}

// PUT
// =============================================================================
exports.put = function ( request, response, next ) {
  var status

  response.model.meta.params = _.extend( response.model.meta.params, request.params )

  exports.update( request.params, request.body ).then(function( data ) {
    response.model.data = data
    response.status( 201 )
    next()
  }, function( error ) {
    response.model.errors.push( error )

    var status = error.code || 400
    response.status( status )
    next()
  })
}

exports.update = function( query, changes ) {
  return new Promise(function(resolve, reject) {
    if ( query.id ) {
      Rescue.findById( query.id, function ( error, rescue ) {
        if ( error ) {
          reject( { error: error, meta: {} } )

        } else if ( !rescue ) {
          reject( { error: ErrorModels.not_found, meta: {} } )

        } else {
          for ( var key in changes ) {
            if ( key === 'client' ) {
              _.extend( rescue.client, changes[key] )
            } else {
              rescue[key] = changes[key]
            }
          }

          rescue.save( function ( error, data ) {
            var errors

            if ( error ) {
              reject( { error: error, meta: {} } )

            } else {
              resolve( { data: data, meta: {} } )
            }
          })
        }
      })
    }
  })
}
<commit_msg>Return data instead of root object to HTTP update endpoints<commit_after>var _, ErrorModels, handleError, mongoose, Rat, Rescue, rescue, save, winston

_ = require( 'underscore' )
mongoose = require( 'mongoose' )
winston = require( 'winston' )

Rat = require( '../models/rat' )
Rescue = require( '../models/rescue' )
ErrorModels = require( '../errors' )

// SHARED FUNCTIONS
// =============================================================================
handleError = function ( error ) {
  errorTypes = Object.keys( error.errors )

  console.error( errorTypes )

  for ( var i = 0; i < errorTypes.length; i++ ) {
    var errorModel, errorType

    errorType = errorTypes[i]
    error = error.errors[errorType].properties

    if ( error.type === 'required' ) {
      errorModel = ErrorModels['missing_required_field']
    }

    errorModel.detail = 'You\'re missing the required field: ' + error.path

    response.model.errors.push( errorModel )
  }
}

// GET
// =============================================================================
exports.get = function ( request, response, next ) {

  exports.read( request.body ).then( function( res ) {
    var data = res.data
    var meta = res.meta

    response.model.data = data
    response.model.meta = meta
    response.status = 400
    next()
  }, function( error ) {
    response.model.errors.push( error.error )
    response.status( 400 )
    next()
  })
}

// GET (by ID)
// =============================================================================
exports.getById = function ( request, response, next ) {
  var id

  response.model.meta.params = _.extend( response.model.meta.params, request.params )
  console.log( response.model.meta.params )

  id = request.params.id

  Rescue
  .findById( id )
  .populate( 'rats' )
  .exec( function ( error, rescue ) {
    var status

    if ( error ) {
      response.model.errors.push( error )
      response.status( 400 )

    } else {
      response.model.data = rescue
      response.status( 200 )
    }

    next()
  })
}

exports.read = function ( query ) {
  return new Promise(function (resolve, reject) {
    var filter

    filter = {}
    dbQuery = {}

    filter.size = parseInt( query.limit ) || 25
    delete query.limit

    filter.from = parseInt( query.offset ) || 0
    delete query.offset

    for ( var key in query ) {
      if ( key === 'q' ) {
        dbQuery.query_string = {
          query: request.body.q
        }
      } else {
        if ( !dbQuery.bool ) {
          dbQuery.bool = {
            should: []
          }
        }

        term = {}
        term[key] = {
          query: query[key],
          fuzziness: 'auto'
        }
        dbQuery.bool.should.push( { match: term } )
      }
    }

    if ( !Object.keys( dbQuery ).length ) {
      dbQuery.match_all = {}
    }

    Rescue.search( dbQuery, filter, function ( error, queryData ) {
      if ( error ) {
        var errorObj = ErrorModels.server_error
        errorObj.detail = error
        reject( { error: errorObj, meta: {} } )

      } else {
        var meta = {
          count: queryData.hits.hits.length,
          limit: filter.size,
          offset: filter.from,
          total: queryData.hits.total
        }

        var data = []

        queryData.hits.hits.forEach( function ( rescue, index, rescues ) {
          var rescueToPopulate, rescueFind

          rescue._source._id = rescue._id
          rescue._source.score = rescue._score

          data.push( rescue._source )
        })

        resolve( { data: data, meta: meta } )
      }
    })
  })
}

// POST
// =============================================================================
exports.post = function ( request, response, next ) {
  exports.create( request.body ).then(function( res ) {
    console.log('done')
    response.model.data = res.data
    response.status( 201 )
    next()
  }, function( error ) {
    console.log('erroring')
    response.model.errors.push( error )
    response.status( 400 )
    next()
  })
}

exports.create = function( query ) {
  return new Promise(function(resolve, reject) {
    console.log('0')
    var finds, firstLimpetFind

    finds = []

    console.log(query)
    // Validate and update rats
    if ( typeof query.rats === 'string' ) {
      query.rats = query.rats.split( ',' )
    }

    query.unidentifiedRats = []

    if ( query.rats ) {
      query.rats.forEach( function ( rat, index, rats ) {
        var find, CMDRname

        if ( typeof rat === 'string' ) {
          if ( !mongoose.Types.ObjectId.isValid( rat ) ) {
            CMDRname = rat.trim()

            query.rats = _.without( query.rats, CMDRname )

            find = Rat.findOne({
              CMDRname: CMDRname
            })

            find.then( function ( rat ) {
              if ( rat ) {
                query.rats.push( rat._id )
              } else {
                query.unidentifiedRats.push( CMDRname )
              }
            })

            finds.push( find )
          }

        } else if ( typeof rat === 'object' && rat._id ) {
          query.rats.push( rat._id )
        }
      })
    }

    // Validate and update firstLimpet
    if ( query.firstLimpet ) {
      if ( typeof query.firstLimpet === 'string' ) {
        if ( !mongoose.Types.ObjectId.isValid( query.firstLimpet ) ) {
          firstLimpetFind = Rat.findOne({
            CMDRname: query.firstLimpet.trim()
          })

          firstLimpetFind.then( function ( rat ) {
            if ( rat ) {
              query.firstLimpet = rat._id
            }
          })

          finds.push( firstLimpetFind )
        }

      } else if ( typeof query.firstLimpet === 'object' && query.firstLimpet._id ) {
        query.firstLimpet = query.firstLimpet._id
      }
    }
    Promise.all( finds )
    .then( function () {

      Rescue.create( query, function ( error, rescue ) {
        var errors

        if ( error ) {
          var errorObj = ErrorModels.server_error
          errorObj.detail = error
          reject( { error: errorObj, meta: {} } )

        } else {
          resolve ( { data: rescue, meta: {} } )
        }
      })
    })
  })
}

// PUT
// =============================================================================
exports.put = function ( request, response, next ) {
  var status

  response.model.meta.params = _.extend( response.model.meta.params, request.params )

  exports.update( request.params, request.body ).then(function( data ) {
    response.model.data = data.data
    response.status( 201 )
    next()
  }, function( error ) {
    response.model.errors.push( error )

    var status = error.code || 400
    response.status( status )
    next()
  })
}

exports.update = function( query, changes ) {
  return new Promise(function(resolve, reject) {
    if ( query.id ) {
      Rescue.findById( query.id, function ( error, rescue ) {
        if ( error ) {
          reject( { error: error, meta: {} } )

        } else if ( !rescue ) {
          reject( { error: ErrorModels.not_found, meta: {} } )

        } else {
          for ( var key in changes ) {
            if ( key === 'client' ) {
              _.extend( rescue.client, changes[key] )
            } else {
              rescue[key] = changes[key]
            }
          }

          rescue.save( function ( error, data ) {
            var errors

            if ( error ) {
              reject( { error: error, meta: {} } )

            } else {
              resolve( { data: data, meta: {} } )
            }
          })
        }
      })
    }
  })
}
<|endoftext|>


<commit_before>'use strict'

const path = require('path')
const ActionHero = require(path.join(__dirname, '/../../index.js'))
const actionhero = new ActionHero.Process()
let api

describe('Core', () => {
  describe('chatRoom', () => {
    beforeAll(async () => {
      api = await actionhero.start()
      for (var room in api.config.general.startingChatRooms) {
        try {
          await api.chatRoom.destroy(room)
          await api.chatRoom.add(room)
        } catch (error) {
          if (!error.toString().match(api.config.errors.connectionRoomExists(room))) { throw error }
        }
      }
    })

    afterAll(async () => { await actionhero.stop() })

    describe('say and clients on separate servers', () => {
      let client1
      let client2
      let client3

      beforeAll(async () => {
        client1 = new api.specHelper.Connection()
        client2 = new api.specHelper.Connection()
        client3 = new api.specHelper.Connection()

        client1.verbs('roomAdd', 'defaultRoom')
        client2.verbs('roomAdd', 'defaultRoom')
        client3.verbs('roomAdd', 'defaultRoom')
        await api.utils.sleep(100)
      })

      afterAll(async () => {
        client1.destroy()
        client2.destroy()
        client3.destroy()
        await api.utils.sleep(100)
      })

      test(
        'all connections can join the default room and client #1 can see them',
        async () => {
          let {room, membersCount} = await client1.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test(
        'all connections can join the default room and client #2 can see them',
        async () => {
          let {room, membersCount} = await client2.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test(
        'all connections can join the default room and client #3 can see them',
        async () => {
          let {room, membersCount} = await client3.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test('clients can communicate across the cluster', async () => {
        await client1.verbs('say', ['defaultRoom', 'Hi', 'from', 'client', '1'])
        await api.utils.sleep(100)

        let {message, room, from} = client2.messages[(client2.messages.length - 1)]
        expect(message).toEqual('Hi from client 1')
        expect(room).toEqual('defaultRoom')
        expect(from).toEqual(client1.id)
      })
    })

    describe('chat', () => {
      beforeEach(async () => {
        try {
          await api.chatRoom.destroy('newRoom')
        } catch (error) {
          // it's fine
        }
      })

      test('can check if rooms exist', async () => {
        let found = await api.chatRoom.exists('defaultRoom')
        expect(found).toEqual(true)
      })

      test('can check if a room does not exist', async () => {
        let found = await api.chatRoom.exists('missingRoom')
        expect(found).toEqual(false)
      })

      test('server can create new room', async () => {
        let room = 'newRoom'
        let found
        found = await api.chatRoom.exists(room)
        expect(found).toEqual(false)
        await api.chatRoom.add(room)
        found = await api.chatRoom.exists(room)
        expect(found).toEqual(true)
      })

      test('server cannot create already existing room', async () => {
        try {
          await api.chatRoom.add('defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: room exists')
        }
      })

      test('can enumerate all the rooms in the system', async () => {
        await api.chatRoom.add('newRoom')
        let rooms = await api.chatRoom.list()
        expect(rooms).toHaveLength(3);
        ['defaultRoom', 'newRoom', 'otherRoom'].forEach((r) => {
          expect(rooms.indexOf(r)).toBeGreaterThan(-1)
        })
      })

      test('server can add connections to a LOCAL room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        expect(client.rooms[0]).toEqual('defaultRoom')
        client.destroy()
      })

      test('will not re-add a member to a room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        try {
          didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: connection already in this room (defaultRoom)')
          client.destroy()
        }
      })

      test('will not add a member to a non-existant room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        try {
          await api.chatRoom.addMember(client.id, 'crazyRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: room does not exist')
          client.destroy()
        }
      })

      test('server will not remove a member not in a room', async () => {
        let client = new api.specHelper.Connection()
        try {
          await api.chatRoom.removeMember(client.id, 'defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: connection not in this room (defaultRoom)')
          client.destroy()
        }
      })

      test('server can remove connections to a room', async () => {
        let client = new api.specHelper.Connection()
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        let didRemove = await api.chatRoom.removeMember(client.id, 'defaultRoom')
        expect(didRemove).toEqual(true)
        client.destroy()
      })

      test('server can destroy a room and connections will be removed', async () => {
        try {
          // to ensure it starts empty
          await api.chatRoom.destroy('newRoom')
        } catch (error) { }

        let client = new api.specHelper.Connection()
        await api.chatRoom.add('newRoom')
        let didAdd = await api.chatRoom.addMember(client.id, 'newRoom')
        expect(didAdd).toEqual(true)
        expect(client.rooms[0]).toEqual('newRoom')

        await api.chatRoom.destroy('newRoom')
        expect(client.rooms).toHaveLength(0)

        // testing for the recepit of this message is a race condition with room.destroy and boradcast in test
        // client.messages[1].message.should.equal('this room has been deleted')
        // client.messages[1].room.should.equal('newRoom')

        client.destroy()
      })

      test('can get a list of room members', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        await api.chatRoom.add('newRoom')
        await api.chatRoom.addMember(client.id, 'newRoom')
        let {room, membersCount} = await api.chatRoom.roomStatus('newRoom')
        expect(room).toEqual('newRoom')
        expect(membersCount).toEqual(1)
        client.destroy()
        await api.chatRoom.destroy('newRoom')
      })

      describe('chat middleware', () => {
        let clientA
        let clientB
        let originalGenerateMessagePayload

        beforeEach(() => {
          originalGenerateMessagePayload = api.chatRoom.generateMessagePayload
          clientA = new api.specHelper.Connection()
          clientB = new api.specHelper.Connection()
        })

        afterEach(() => {
          api.chatRoom.middleware = {}
          api.chatRoom.globalMiddleware = []

          clientA.destroy()
          clientB.destroy()

          api.chatRoom.generateMessagePayload = originalGenerateMessagePayload
        })

        test('generateMessagePayload can be overloaded', async () => {
          api.chatRoom.generateMessagePayload = (message) => {
            return {
              thing: 'stuff',
              room: message.connection.room,
              from: message.connection.id
            }
          }

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientA.verbs('say', ['defaultRoom', 'hi there'])
          await api.utils.sleep(100)
          let message = clientB.messages[(clientB.messages.length - 1)]
          expect(message.thing).toEqual('stuff')
          expect(message.message).toBeUndefined()
        })

        test('(join + leave) can add middleware to announce members', async () => {
          api.chatRoom.addMiddleware({
            name: 'add chat middleware',
            join: async (connection, room) => {
              await api.chatRoom.broadcast({}, room, `I have entered the room: ${connection.id}`)
            }
          })

          api.chatRoom.addMiddleware({
            name: 'leave chat middleware',
            leave: async (connection, room) => {
              await api.chatRoom.broadcast({}, room, `I have left the room: ${connection.id}`)
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomLeave', 'defaultRoom')
          await api.utils.sleep(100)

          expect(clientA.messages.pop().message).toEqual('I have left the room: ' + clientB.id)
          expect(clientA.messages.pop().message).toEqual('I have entered the room: ' + clientB.id)
        })

        test('(say) can modify message payloads', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: async (connection, room, messagePayload) => {
              if (messagePayload.from !== 0) { messagePayload.message = 'something else' }
              return messagePayload
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)

          let lastMessage = clientA.messages[(clientA.messages.length - 1)]
          expect(lastMessage.message).toEqual('something else')
        })

        test(
          'can add middleware in a particular order and will be passed modified messagePayloads',
          async () => {
            api.chatRoom.addMiddleware({
              name: 'chat middleware 1',
              priority: 1000,
              say: async (connection, room, messagePayload, callback) => {
                messagePayload.message = 'MIDDLEWARE 1'
                return messagePayload
              }
            })

            api.chatRoom.addMiddleware({
              name: 'chat middleware 2',
              priority: 2000,
              say: async (connection, room, messagePayload) => {
                messagePayload.message = messagePayload.message + ' MIDDLEWARE 2'
                return messagePayload
              }
            })

            await clientA.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
            await api.utils.sleep(100)

            let lastMessage = clientA.messages[(clientA.messages.length - 1)]
            expect(lastMessage.message).toEqual('MIDDLEWARE 1 MIDDLEWARE 2')
          }
        )

        test('say middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: async (connection, room, messagePayload) => {
              throw new Error('messages blocked')
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)

          // welcome message is passed, no join/leave/or say messages
          expect(clientA.messages).toHaveLength(1)
          expect(clientA.messages[0].welcome).toMatch(/Welcome/)
        })

        test('join middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            join: async (connection, room) => {
              throw new Error('joining rooms blocked')
            }
          })

          try {
            await clientA.verbs('roomAdd', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: joining rooms blocked')
            expect(clientA.rooms).toHaveLength(0)
          }
        })

        test('leave middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            leave: async (connection, room) => {
              throw new Error('Hotel California')
            }
          })

          let didJoin = await clientA.verbs('roomAdd', 'defaultRoom')
          expect(didJoin).toEqual(true)
          expect(clientA.rooms).toHaveLength(1)
          expect(clientA.rooms[0]).toEqual('defaultRoom')

          try {
            await clientA.verbs('roomLeave', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: Hotel California')
            expect(clientA.rooms).toHaveLength(1)
          }
        })
      })
    })
  })
})
<commit_msg>Rolled back the test functions to not have async keyword. Replicated all the tests that were done without the async keyword, and added the async key word, also updated test description to notify that is an async function the one in middleware.<commit_after>'use strict'

const path = require('path')
const ActionHero = require(path.join(__dirname, '/../../index.js'))
const actionhero = new ActionHero.Process()
let api

describe('Core', () => {
  describe('chatRoom', () => {
    beforeAll(async () => {
      api = await actionhero.start()
      for (var room in api.config.general.startingChatRooms) {
        try {
          await api.chatRoom.destroy(room)
          await api.chatRoom.add(room)
        } catch (error) {
          if (!error.toString().match(api.config.errors.connectionRoomExists(room))) { throw error }
        }
      }
    })

    afterAll(async () => { await actionhero.stop() })

    describe('say and clients on separate servers', () => {
      let client1
      let client2
      let client3

      beforeAll(async () => {
        client1 = new api.specHelper.Connection()
        client2 = new api.specHelper.Connection()
        client3 = new api.specHelper.Connection()

        client1.verbs('roomAdd', 'defaultRoom')
        client2.verbs('roomAdd', 'defaultRoom')
        client3.verbs('roomAdd', 'defaultRoom')
        await api.utils.sleep(100)
      })

      afterAll(async () => {
        client1.destroy()
        client2.destroy()
        client3.destroy()
        await api.utils.sleep(100)
      })

      test(
        'all connections can join the default room and client #1 can see them',
        async () => {
          let {room, membersCount} = await client1.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test(
        'all connections can join the default room and client #2 can see them',
        async () => {
          let {room, membersCount} = await client2.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test(
        'all connections can join the default room and client #3 can see them',
        async () => {
          let {room, membersCount} = await client3.verbs('roomView', 'defaultRoom')
          expect(room).toEqual('defaultRoom')
          expect(membersCount).toEqual(3)
        }
      )

      test('clients can communicate across the cluster', async () => {
        await client1.verbs('say', ['defaultRoom', 'Hi', 'from', 'client', '1'])
        await api.utils.sleep(100)

        let {message, room, from} = client2.messages[(client2.messages.length - 1)]
        expect(message).toEqual('Hi from client 1')
        expect(room).toEqual('defaultRoom')
        expect(from).toEqual(client1.id)
      })
    })

    describe('chat', () => {
      beforeEach(async () => {
        try {
          await api.chatRoom.destroy('newRoom')
        } catch (error) {
          // it's fine
        }
      })

      test('can check if rooms exist', async () => {
        let found = await api.chatRoom.exists('defaultRoom')
        expect(found).toEqual(true)
      })

      test('can check if a room does not exist', async () => {
        let found = await api.chatRoom.exists('missingRoom')
        expect(found).toEqual(false)
      })

      test('server can create new room', async () => {
        let room = 'newRoom'
        let found
        found = await api.chatRoom.exists(room)
        expect(found).toEqual(false)
        await api.chatRoom.add(room)
        found = await api.chatRoom.exists(room)
        expect(found).toEqual(true)
      })

      test('server cannot create already existing room', async () => {
        try {
          await api.chatRoom.add('defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: room exists')
        }
      })

      test('can enumerate all the rooms in the system', async () => {
        await api.chatRoom.add('newRoom')
        let rooms = await api.chatRoom.list()
        expect(rooms).toHaveLength(3);
        ['defaultRoom', 'newRoom', 'otherRoom'].forEach((r) => {
          expect(rooms.indexOf(r)).toBeGreaterThan(-1)
        })
      })

      test('server can add connections to a LOCAL room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        expect(client.rooms[0]).toEqual('defaultRoom')
        client.destroy()
      })

      test('will not re-add a member to a room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        try {
          didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: connection already in this room (defaultRoom)')
          client.destroy()
        }
      })

      test('will not add a member to a non-existant room', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        try {
          await api.chatRoom.addMember(client.id, 'crazyRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: room does not exist')
          client.destroy()
        }
      })

      test('server will not remove a member not in a room', async () => {
        let client = new api.specHelper.Connection()
        try {
          await api.chatRoom.removeMember(client.id, 'defaultRoom')
          throw new Error('should not get here')
        } catch (error) {
          expect(error.toString()).toEqual('Error: connection not in this room (defaultRoom)')
          client.destroy()
        }
      })

      test('server can remove connections to a room', async () => {
        let client = new api.specHelper.Connection()
        let didAdd = await api.chatRoom.addMember(client.id, 'defaultRoom')
        expect(didAdd).toEqual(true)
        let didRemove = await api.chatRoom.removeMember(client.id, 'defaultRoom')
        expect(didRemove).toEqual(true)
        client.destroy()
      })

      test('server can destroy a room and connections will be removed', async () => {
        try {
          // to ensure it starts empty
          await api.chatRoom.destroy('newRoom')
        } catch (error) { }

        let client = new api.specHelper.Connection()
        await api.chatRoom.add('newRoom')
        let didAdd = await api.chatRoom.addMember(client.id, 'newRoom')
        expect(didAdd).toEqual(true)
        expect(client.rooms[0]).toEqual('newRoom')

        await api.chatRoom.destroy('newRoom')
        expect(client.rooms).toHaveLength(0)

        // testing for the recepit of this message is a race condition with room.destroy and boradcast in test
        // client.messages[1].message.should.equal('this room has been deleted')
        // client.messages[1].room.should.equal('newRoom')

        client.destroy()
      })

      test('can get a list of room members', async () => {
        let client = new api.specHelper.Connection()
        expect(client.rooms).toHaveLength(0)
        await api.chatRoom.add('newRoom')
        await api.chatRoom.addMember(client.id, 'newRoom')
        let {room, membersCount} = await api.chatRoom.roomStatus('newRoom')
        expect(room).toEqual('newRoom')
        expect(membersCount).toEqual(1)
        client.destroy()
        await api.chatRoom.destroy('newRoom')
      })

      describe('chat middleware', () => {
        let clientA
        let clientB
        let originalGenerateMessagePayload

        beforeEach(() => {
          originalGenerateMessagePayload = api.chatRoom.generateMessagePayload
          clientA = new api.specHelper.Connection()
          clientB = new api.specHelper.Connection()
        })

        afterEach(() => {
          api.chatRoom.middleware = {}
          api.chatRoom.globalMiddleware = []

          clientA.destroy()
          clientB.destroy()

          api.chatRoom.generateMessagePayload = originalGenerateMessagePayload
        })

        test('generateMessagePayload can be overloaded', async () => {
          api.chatRoom.generateMessagePayload = (message) => {
            return {
              thing: 'stuff',
              room: message.connection.room,
              from: message.connection.id
            }
          }

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientA.verbs('say', ['defaultRoom', 'hi there'])
          await api.utils.sleep(100)
          let message = clientB.messages[(clientB.messages.length - 1)]
          expect(message.thing).toEqual('stuff')
          expect(message.message).toBeUndefined()
        })

        test('(join + leave) can add middleware to announce members', async () => {
          api.chatRoom.addMiddleware({
            name: 'add chat middleware',
            join: async (connection, room) => {
              await api.chatRoom.broadcast({}, room, `I have entered the room: ${connection.id}`)
            }
          })

          api.chatRoom.addMiddleware({
            name: 'leave chat middleware',
            leave: async (connection, room) => {
              await api.chatRoom.broadcast({}, room, `I have left the room: ${connection.id}`)
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomLeave', 'defaultRoom')
          await api.utils.sleep(100)

          expect(clientA.messages.pop().message).toEqual('I have left the room: ' + clientB.id)
          expect(clientA.messages.pop().message).toEqual('I have entered the room: ' + clientB.id)
        })

        test('(say) can modify message payloads', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: (connection, room, messagePayload) => {
              if (messagePayload.from !== 0) { messagePayload.message = 'something else' }
              return messagePayload
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)

          let lastMessage = clientA.messages[(clientA.messages.length - 1)]
          expect(lastMessage.message).toEqual('something else')
        })

        test(
          'can add middleware in a particular order and will be passed modified messagePayloads',
          async () => {
            api.chatRoom.addMiddleware({
              name: 'chat middleware 1',
              priority: 1000,
              say: (connection, room, messagePayload, callback) => {
                messagePayload.message = 'MIDDLEWARE 1'
                return messagePayload
              }
            })

            api.chatRoom.addMiddleware({
              name: 'chat middleware 2',
              priority: 2000,
              say: async (connection, room, messagePayload) => {
                messagePayload.message = messagePayload.message + ' MIDDLEWARE 2'
                return messagePayload
              }
            })

            await clientA.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
            await api.utils.sleep(100)

            let lastMessage = clientA.messages[(clientA.messages.length - 1)]
            expect(lastMessage.message).toEqual('MIDDLEWARE 1 MIDDLEWARE 2')
          }
        )

        test('say middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: (connection, room, messagePayload) => {
              throw new Error('messages blocked')
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)

          // welcome message is passed, no join/leave/or say messages
          expect(clientA.messages).toHaveLength(1)
          expect(clientA.messages[0].welcome).toMatch(/Welcome/)
        })

        test('join middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            join: (connection, room) => {
              throw new Error('joining rooms blocked')
            }
          })

          try {
            await clientA.verbs('roomAdd', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: joining rooms blocked')
            expect(clientA.rooms).toHaveLength(0)
          }
        })

        test('leave middleware can block excecution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            leave: (connection, room) => {
              throw new Error('Hotel California')
            }
          })

          let didJoin = await clientA.verbs('roomAdd', 'defaultRoom')
          expect(didJoin).toEqual(true)
          expect(clientA.rooms).toHaveLength(1)
          expect(clientA.rooms[0]).toEqual('defaultRoom')

          try {
            await clientA.verbs('roomLeave', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: Hotel California')
            expect(clientA.rooms).toHaveLength(1)
          }
        })

        test('(say verb with async keyword) can modify message payloads', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: async (connection, room, messagePayload) => {
              if (messagePayload.from !== 0) { messagePayload.message = 'something else' }
              return messagePayload
            }
          })
          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)
          let lastMessage = clientA.messages[(clientA.messages.length - 1)]
          expect(lastMessage.message).toEqual('something else')
        })

        test(
          'can add middleware in a particular order and will be passed modified messagePayloads with both being async functions',
          async () => {
            api.chatRoom.addMiddleware({
              name: 'chat middleware 1',
              priority: 1000,
              say: async (connection, room, messagePayload, callback) => {
                messagePayload.message = 'MIDDLEWARE 1'
                return messagePayload
              }
            })

            api.chatRoom.addMiddleware({
              name: 'chat middleware 2',
              priority: 2000,
              say: async (connection, room, messagePayload) => {
                messagePayload.message = messagePayload.message + ' MIDDLEWARE 2'
                return messagePayload
              }
            })

            await clientA.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('roomAdd', 'defaultRoom')
            await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
            await api.utils.sleep(100)

            let lastMessage = clientA.messages[(clientA.messages.length - 1)]
            expect(lastMessage.message).toEqual('MIDDLEWARE 1 MIDDLEWARE 2')
          }
        )

        test('say async function middleware can block execution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            say: async (connection, room, messagePayload) => {
              throw new Error('messages blocked')
            }
          })

          await clientA.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('roomAdd', 'defaultRoom')
          await clientB.verbs('say', ['defaultRoom', 'something', 'awesome'])
          await api.utils.sleep(100)

          // welcome message is passed, no join/leave/or say messages
          expect(clientA.messages).toHaveLength(1)
          expect(clientA.messages[0].welcome).toMatch(/Welcome/)
        })

        test('join async function middleware can block execution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            join: async (connection, room) => {
              throw new Error('joining rooms blocked')
            }
          })

          try {
            await clientA.verbs('roomAdd', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: joining rooms blocked')
            expect(clientA.rooms).toHaveLength(0)
          }
        })

        test('leave async function middleware can block execution', async () => {
          api.chatRoom.addMiddleware({
            name: 'chat middleware',
            leave: async (connection, room) => {
              throw new Error('Hotel California')
            }
          })

          let didJoin = await clientA.verbs('roomAdd', 'defaultRoom')
          expect(didJoin).toEqual(true)
          expect(clientA.rooms).toHaveLength(1)
          expect(clientA.rooms[0]).toEqual('defaultRoom')

          try {
            await clientA.verbs('roomLeave', 'defaultRoom')
            throw new Error('should not get here')
          } catch (error) {
            expect(error.toString()).toEqual('Error: Hotel California')
            expect(clientA.rooms).toHaveLength(1)
          }
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

class Cards {
  constructor () {
    this.cards = Array.from(document.querySelectorAll('.card'));

    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);
    this.update = this.update.bind(this);
    this.targetBCR = null;
    this.target = null;
    this.startX = 0;
    this.currentX = 0;
    this.screenX = 0;
    this.targetX = 0;
    this.draggingCard = false;

    this.addEventListeners();

    requestAnimationFrame(this.update);
  }

  addEventListeners () {
    document.addEventListener('touchstart', this.onStart);
    document.addEventListener('touchmove', this.onMove);
    document.addEventListener('touchend', this.onEnd);

    document.addEventListener('mousedown', this.onStart);
    document.addEventListener('mousemove', this.onMove);
    document.addEventListener('mouseup', this.onEnd);
  }

  onStart (evt) {
    if (this.target)
      return;

    if (!evt.target.classList.contains('card'))
      return;

    this.target = evt.target;
    this.targetBCR = this.target.getBoundingClientRect();

    this.startX = evt.pageX || evt.touches[0].pageX;
    this.currentX = this.startX;

    this.draggingCard = true;
    this.target.style.willChange = 'transform';

    evt.preventDefault();
  }

  onMove (evt) {
    if (!this.target)
      return;

    this.currentX = evt.pageX || evt.touches[0].pageX;
  }

  onEnd (evt) {
    if (!this.target)
      return;

    this.targetX = 0;
    let screenX = this.currentX - this.startX;
    if (Math.abs(screenX) > this.targetBCR.width * 0.35) {
      this.targetX
          = (screenX > 0) ? this.targetBCR.width : -this.targetBCR.width;
    }

    this.draggingCard = false;
  }

  update () {

    requestAnimationFrame(this.update);

    if (!this.target)
      return;

    if (this.draggingCard) {
      this.screenX = this.currentX - this.startX;
    } else {
      this.screenX += (this.targetX - this.screenX) / 4;
    }

    const normalizedDragDistance =
        (Math.abs(this.screenX) / this.targetBCR.width);
    const opacity = 1 - Math.pow(normalizedDragDistance, 3);

    this.target.style.transform = `translateX(${this.screenX}px)`;
    this.target.style.opacity = opacity;

    // User has finished dragging.
    if (this.draggingCard)
      return;

    const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
    const isNearlyInvisible = (opacity < 0.01);

    // If the card is nearly gone.
    if (isNearlyInvisible) {

      // Bail if there's no target or it's not attached to a parent anymore.
      if (!this.target || !this.target.parentNode)
        return;

      this.target.parentNode.removeChild(this.target);

      const targetIndex = this.cards.indexOf(this.target);
      this.cards.splice(targetIndex, 1);

      // Slide all the other cards.
      this.animateOtherCardsIntoPosition(targetIndex);

    } else if (isNearlyAtStart) {
      this.resetTarget();
    }
  }

  animateOtherCardsIntoPosition (startIndex) {
    // If removed card was the last one, there is nothing to animate. Remove target.
    if (startIndex === this.cards.length) {
      this.resetTarget();
      return;
    }

    const frames = [{
      transform: `translateY(${this.targetBCR.height + 20}px)`
    }, {
      transform: 'none'
    }];
    const options = {
      easing: 'cubic-bezier(0,0,0.31,1)',
      duration: 150
    };
    const onAnimationComplete = () => this.resetTarget();

    for (let i = startIndex; i < this.cards.length; i++) {
      const card = this.cards[i];

      // Move the card down then slide it up.
      card
        .animate(frames, options)
        .addEventListener('finish', onAnimationComplete);
    }
  }

  resetTarget () {
    if (!this.target)
      return;

    this.target.style.willChange = 'initial';
    this.target.style.transform = 'none';
    this.target = null;
  }
}

window.addEventListener('load', () => new Cards());
<commit_msg>Tidies code.<commit_after>/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

class Cards {
  constructor () {
    this.cards = Array.from(document.querySelectorAll('.card'));

    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);
    this.update = this.update.bind(this);
    this.targetBCR = null;
    this.target = null;
    this.startX = 0;
    this.currentX = 0;
    this.screenX = 0;
    this.targetX = 0;
    this.draggingCard = false;

    this.addEventListeners();

    requestAnimationFrame(this.update);
  }

  addEventListeners () {
    document.addEventListener('touchstart', this.onStart);
    document.addEventListener('touchmove', this.onMove);
    document.addEventListener('touchend', this.onEnd);

    document.addEventListener('mousedown', this.onStart);
    document.addEventListener('mousemove', this.onMove);
    document.addEventListener('mouseup', this.onEnd);
  }

  onStart (evt) {
    if (this.target)
      return;

    if (!evt.target.classList.contains('card'))
      return;

    this.target = evt.target;
    this.targetBCR = this.target.getBoundingClientRect();

    this.startX = evt.pageX || evt.touches[0].pageX;
    this.currentX = this.startX;

    this.draggingCard = true;
    this.target.style.willChange = 'transform';

    evt.preventDefault();
  }

  onMove (evt) {
    if (!this.target)
      return;

    this.currentX = evt.pageX || evt.touches[0].pageX;
  }

  onEnd (evt) {
    if (!this.target)
      return;

    this.targetX = 0;
    let screenX = this.currentX - this.startX;
    const threshold = this.targetBCR.width * 0.35;
    if (Math.abs(screenX) > threshold) {
      this.targetX = (screenX > 0) ?
           this.targetBCR.width :
          -this.targetBCR.width;
    }

    this.draggingCard = false;
  }

  update () {

    requestAnimationFrame(this.update);

    if (!this.target)
      return;

    if (this.draggingCard) {
      this.screenX = this.currentX - this.startX;
    } else {
      this.screenX += (this.targetX - this.screenX) / 4;
    }

    const normalizedDragDistance =
        (Math.abs(this.screenX) / this.targetBCR.width);
    const opacity = 1 - Math.pow(normalizedDragDistance, 3);

    this.target.style.transform = `translateX(${this.screenX}px)`;
    this.target.style.opacity = opacity;

    // User has finished dragging.
    if (this.draggingCard)
      return;

    const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
    const isNearlyInvisible = (opacity < 0.01);

    // If the card is nearly gone.
    if (isNearlyInvisible) {

      // Bail if there's no target or it's not attached to a parent anymore.
      if (!this.target || !this.target.parentNode)
        return;

      this.target.parentNode.removeChild(this.target);

      const targetIndex = this.cards.indexOf(this.target);
      this.cards.splice(targetIndex, 1);

      // Slide all the other cards.
      this.animateOtherCardsIntoPosition(targetIndex);

    } else if (isNearlyAtStart) {
      this.resetTarget();
    }
  }

  animateOtherCardsIntoPosition (startIndex) {
    // If removed card was the last one, there is nothing to animate.
    // Remove the target.
    if (startIndex === this.cards.length) {
      this.resetTarget();
      return;
    }

    const frames = [{
      transform: `translateY(${this.targetBCR.height + 20}px)`
    }, {
      transform: 'none'
    }];
    const options = {
      easing: 'cubic-bezier(0,0,0.31,1)',
      duration: 150
    };
    const onAnimationComplete = () => this.resetTarget();

    for (let i = startIndex; i < this.cards.length; i++) {
      const card = this.cards[i];

      // Move the card down then slide it up.
      card
        .animate(frames, options)
        .addEventListener('finish', onAnimationComplete);
    }
  }

  resetTarget () {
    if (!this.target)
      return;

    this.target.style.willChange = 'initial';
    this.target.style.transform = 'none';
    this.target = null;
  }
}

window.addEventListener('load', () => new Cards());
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2021 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {fireEvent, render, waitFor} from '@testing-library/react'
import React from 'react'
import MentionDropdown from '../MentionDropdown'
import FakeEditor from '@instructure/canvas-rce/src/rce/plugins/shared/__tests__/FakeEditor'
import tinymce from 'tinymce'
import getPosition from '../getPosition'
import {ARIA_ID_TEMPLATES} from '../../../constants'
import {nanoid} from 'nanoid'
import {handlers, MentionMockUsers} from '../graphql/mswHandlers'
import {mswClient} from '../../../../../../msw/mswClient'
import {mswServer} from '../../../../../../msw/mswServer'
import {ApolloProvider} from 'react-apollo'
import {graphql} from 'msw'

const mockedEditor = {
  editor: {
    id: nanoid()
  }
}

jest.mock('../getPosition')

describe('Mention Dropdown', () => {
  const server = mswServer(handlers)

  beforeAll(() => {
    getPosition.mockImplementation(() => {
      return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0}
    })

    // eslint-disable-next-line no-undef
    fetchMock.dontMock()
    server.listen()
  })

  beforeEach(() => {
    mswClient.cache.reset()
    getPosition.mockClear()
    const editor = new FakeEditor()
    editor.getParam = () => {
      return 'LTR'
    }
    tinymce.activeEditor = editor
  })

  afterEach(() => {
    jest.clearAllMocks()
    server.resetHandlers()
  })

  afterAll(() => {
    server.close()
    // eslint-disable-next-line no-undef
    fetchMock.enableMocks()
  })

  const setup = props => {
    return render(
      <ApolloProvider client={mswClient}>
        <MentionDropdown editor={mockedEditor} {...props} />
      </ApolloProvider>
    )
  }

  describe('Rendering', () => {
    it('should render component', () => {
      const component = setup()
      expect(component).toBeTruthy()
    })
  })

  describe('Events', () => {
    it('should attach resize event handler', () => {
      global.addEventListener = jest.fn()
      setup()
      const eventListenerList = global.addEventListener.mock.calls.map(el => {
        return el[0]
      })
      expect(eventListenerList).toContain('resize')
    })

    it('should attach scroll event handler', () => {
      global.addEventListener = jest.fn()
      setup()
      const eventListenerList = global.addEventListener.mock.calls.map(el => {
        return el[0]
      })
      expect(eventListenerList).toContain('scroll')
    })
  })

  describe('Positioning', () => {
    it('should called getXYPosition on load', () => {
      setup()
      expect(getPosition.mock.calls.length).toBe(1)
    })
  })

  describe('Callbacks', () => {
    it.skip('should call onFocusedUserChangeMock when user changes', async () => {
      const onFocusedUserChangeMock = jest.fn()
      const {getAllByTestId} = setup({
        onFocusedUserChange: onFocusedUserChangeMock
      })
      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))

      expect(onFocusedUserChangeMock.mock.calls.length).toBe(2)

      const menuItems = await waitFor(() => getAllByTestId('mention-dropdown-item'))
      fireEvent.click(menuItems[3].querySelector('li'))

      // Expect 2 re-renders per click totalling 4
      expect(onFocusedUserChangeMock.mock.calls.length).toBe(4)
    })
  })

  describe('accessibility', () => {
    it('should call ARIA_ID_TEMPLATES and pass to callback', async () => {
      const onActiveDescendantChangeMock = jest.fn()
      const spy = jest.spyOn(ARIA_ID_TEMPLATES, 'activeDescendant')
      const {getAllByTestId} = setup({
        onActiveDescendantChange: onActiveDescendantChangeMock
      })

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))

      const menuItems = await waitFor(() => getAllByTestId('mention-dropdown-item'))
      fireEvent.click(menuItems[1].querySelector('li'))

      expect(spy).toHaveBeenCalled()
    })
  })

  describe('graphql', () => {
    it.skip('should have 10 mentionable users using default handler', async () => {
      const {getAllByTestId} = setup()

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))
    })

    it.skip('should have 4 mentionable users using custom handler', async () => {
      server.use(
        graphql.query('GetMentionableUsers', (req, res, ctx) => {
          return res(
            ctx.data({
              legacyNode: {
                id: 'Vxb',
                mentionableUsersConnection: {
                  nodes: MentionMockUsers.slice(0, 4),
                  __typename: 'MessageableUserConnection'
                },
                __typename: 'Discussion'
              }
            })
          )
        })
      )

      const {getAllByTestId} = setup()

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(4))
    })

    it.skip('should render name', async () => {
      const {getByText} = setup()

      await waitFor(() => expect(getByText('Rob Orton')).toBeTruthy())
      await waitFor(() => expect(getByText('Caleb Guanzon')).toBeTruthy())
    })
  })
})
<commit_msg>Skip ARIA-Template Mentions test<commit_after>/*
 * Copyright (C) 2021 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {fireEvent, render, waitFor} from '@testing-library/react'
import React from 'react'
import MentionDropdown from '../MentionDropdown'
import FakeEditor from '@instructure/canvas-rce/src/rce/plugins/shared/__tests__/FakeEditor'
import tinymce from 'tinymce'
import getPosition from '../getPosition'
import {ARIA_ID_TEMPLATES} from '../../../constants'
import {nanoid} from 'nanoid'
import {handlers, MentionMockUsers} from '../graphql/mswHandlers'
import {mswClient} from '../../../../../../msw/mswClient'
import {mswServer} from '../../../../../../msw/mswServer'
import {ApolloProvider} from 'react-apollo'
import {graphql} from 'msw'

const mockedEditor = {
  editor: {
    id: nanoid()
  }
}

jest.mock('../getPosition')

describe('Mention Dropdown', () => {
  const server = mswServer(handlers)

  beforeAll(() => {
    getPosition.mockImplementation(() => {
      return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0}
    })

    // eslint-disable-next-line no-undef
    fetchMock.dontMock()
    server.listen()
  })

  beforeEach(() => {
    mswClient.cache.reset()
    getPosition.mockClear()
    const editor = new FakeEditor()
    editor.getParam = () => {
      return 'LTR'
    }
    tinymce.activeEditor = editor
  })

  afterEach(() => {
    jest.clearAllMocks()
    server.resetHandlers()
  })

  afterAll(() => {
    server.close()
    // eslint-disable-next-line no-undef
    fetchMock.enableMocks()
  })

  const setup = props => {
    return render(
      <ApolloProvider client={mswClient}>
        <MentionDropdown editor={mockedEditor} {...props} />
      </ApolloProvider>
    )
  }

  describe('Rendering', () => {
    it('should render component', () => {
      const component = setup()
      expect(component).toBeTruthy()
    })
  })

  describe('Events', () => {
    it('should attach resize event handler', () => {
      global.addEventListener = jest.fn()
      setup()
      const eventListenerList = global.addEventListener.mock.calls.map(el => {
        return el[0]
      })
      expect(eventListenerList).toContain('resize')
    })

    it('should attach scroll event handler', () => {
      global.addEventListener = jest.fn()
      setup()
      const eventListenerList = global.addEventListener.mock.calls.map(el => {
        return el[0]
      })
      expect(eventListenerList).toContain('scroll')
    })
  })

  describe('Positioning', () => {
    it('should called getXYPosition on load', () => {
      setup()
      expect(getPosition.mock.calls.length).toBe(1)
    })
  })

  describe('Callbacks', () => {
    it.skip('should call onFocusedUserChangeMock when user changes', async () => {
      const onFocusedUserChangeMock = jest.fn()
      const {getAllByTestId} = setup({
        onFocusedUserChange: onFocusedUserChangeMock
      })
      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))

      expect(onFocusedUserChangeMock.mock.calls.length).toBe(2)

      const menuItems = await waitFor(() => getAllByTestId('mention-dropdown-item'))
      fireEvent.click(menuItems[3].querySelector('li'))

      // Expect 2 re-renders per click totalling 4
      expect(onFocusedUserChangeMock.mock.calls.length).toBe(4)
    })
  })

  describe('accessibility', () => {
    it.skip('should call ARIA_ID_TEMPLATES and pass to callback', async () => {
      const onActiveDescendantChangeMock = jest.fn()
      const spy = jest.spyOn(ARIA_ID_TEMPLATES, 'activeDescendant')
      const {getAllByTestId} = setup({
        onActiveDescendantChange: onActiveDescendantChangeMock
      })

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))

      const menuItems = await waitFor(() => getAllByTestId('mention-dropdown-item'))
      fireEvent.click(menuItems[1].querySelector('li'))

      expect(spy).toHaveBeenCalled()
    })
  })

  describe('graphql', () => {
    it.skip('should have 10 mentionable users using default handler', async () => {
      const {getAllByTestId} = setup()

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(10))
    })

    it.skip('should have 4 mentionable users using custom handler', async () => {
      server.use(
        graphql.query('GetMentionableUsers', (req, res, ctx) => {
          return res(
            ctx.data({
              legacyNode: {
                id: 'Vxb',
                mentionableUsersConnection: {
                  nodes: MentionMockUsers.slice(0, 4),
                  __typename: 'MessageableUserConnection'
                },
                __typename: 'Discussion'
              }
            })
          )
        })
      )

      const {getAllByTestId} = setup()

      await waitFor(() => expect(getAllByTestId('mention-dropdown-item').length).toBe(4))
    })

    it.skip('should render name', async () => {
      const {getByText} = setup()

      await waitFor(() => expect(getByText('Rob Orton')).toBeTruthy())
      await waitFor(() => expect(getByText('Caleb Guanzon')).toBeTruthy())
    })
  })
})
<|endoftext|>