text: /* eslint-env browser */
  
  /**
   * Module dependencies.
   */
  
  var basename = require('path').basename;
  var debug = require('debug')('mocha:watch');
  var exists = require('fs').existsSync || require('path').existsSync;
  var glob = require('glob');
  var join = require('path').join;
  var readdirSync = require('fs').readdirSync;
  var statSync = require('fs').statSync;
  var watchFile = require('fs').watchFile;
  
  /**
   * Ignored directories.
   */
  
  var ignore = ['node_modules', '.git'];
  
  /**
   * Escape special characters in the given string of html.
   *
   * @api private
   * @param  {string} html
   * @return {string}
   */
  exports.escape = function(html) {
    return String(html)
      .replace(/&/g, '&amp;')
      .replace(/"/g, '&quot;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
  };
  
  /**
   * Array#forEach (<=IE8)
   *
   * @api private
   * @param {Array} arr
   * @param {Function} fn
   * @param {Object} scope
   */
  exports.forEach = function(arr, fn, scope) {
    for (var i = 0, l = arr.length; i < l; i++) {
      fn.call(scope, arr[i], i);
    }
  };
  
  /**
   * Test if the given obj is type of string.
   *
   * @api private
   * @param {Object} obj
   * @return {boolean}
   */
  exports.isString = function(obj) {
    return typeof obj === 'string';
  };
  
  /**
   * Array#map (<=IE8)
   *
   * @api private
   * @param {Array} arr
   * @param {Function} fn
   * @param {Object} scope
   * @return {Array}
   */
  exports.map = function(arr, fn, scope) {
    var result = [];
    for (var i = 0, l = arr.length; i < l; i++) {
      result.push(fn.call(scope, arr[i], i, arr));
    }
    return result;
  };
  
  /**
   * Array#indexOf (<=IE8)
   *
   * @api private
   * @param {Array} arr
   * @param {Object} obj to find index of
   * @param {number} start
   * @return {number}
   */
  exports.indexOf = function(arr, obj, start) {
    for (var i = start || 0, l = arr.length; i < l; i++) {
      if (arr[i] === obj) {
        return i;
      }
    }
    return -1;
  };
  
  /**
   * Array#reduce (<=IE8)
   *
   * @api private
   * @param {Array} arr
   * @param {Function} fn
   * @param {Object} val Initial value.
   * @return {*}
   */
  exports.reduce = function(arr, fn, val) {
    var rval = val;
  
    for (var i = 0, l = arr.length; i < l; i++) {
      rval = fn(rval, arr[i], i, arr);
    }
  
    return rval;
  };
  
  /**
   * Array#filter (<=IE8)
   *
   * @api private
   * @param {Array} arr
   * @param {Function} fn
   * @return {Array}
   */
  exports.filter = function(arr, fn) {
    var ret = [];
  
    for (var i = 0, l = arr.length; i < l; i++) {
      var val = arr[i];
      if (fn(val, i, arr)) {
        ret.push(val);
      }
    }
  
    return ret;
  };
  
  /**
   * Object.keys (<=IE8)
   *
   * @api private
   * @param {Object} obj
   * @return {Array} keys
   */
  exports.keys = typeof Object.keys === 'function' ? Object.keys : function(obj) {
    var keys = [];
    var has = Object.prototype.hasOwnProperty; // for `window` on <=IE8
  
    for (var key in obj) {
      if (has.call(obj, key)) {
        keys.push(key);
      }
    }
  
    return keys;
  };
  
  /**
   * Watch the given `files` for changes
   * and invoke `fn(file)` on modification.
   *
   * @api private
   * @param {Array} files
   * @param {Function} fn
   */
  exports.watch = function(files, fn) {
    var options = { interval: 100 };
    files.forEach(function(file) {
      debug('file %s', file);
      watchFile(file, options, function(curr, prev) {
        if (prev.mtime < curr.mtime) {
          fn(file);
        }
      });
    });
  };
  
  /**
   * Array.isArray (<=IE8)
   *
   * @api private
   * @param {Object} obj
   * @return {Boolean}
   */
  var isArray = typeof Array.isArray === 'function' ? Array.isArray : function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  };
  
  /**
   * Buffer.prototype.toJSON polyfill.
   *
   * @type {Function}
   */
  if (typeof Buffer !== 'undefined' && Buffer.prototype) {
    Buffer.prototype.toJSON = Buffer.prototype.toJSON || function() {
      return Array.prototype.slice.call(this, 0);
    };
  }
  
  /**
   * Ignored files.
   *
   * @api private
   * @param {string} path
   * @return {boolean}
   */
  function ignored(path) {
    return !~ignore.indexOf(path);
  }
  
  /**
   * Lookup files in the given `dir`.
   *
   * @api private
   * @param {string} dir
   * @param {string[]} [ext=['.js']]
   * @param {Array} [ret=[]]
   * @return {Array}
   */
  exports.files = function(dir, ext, ret) {
    ret = ret || [];
    ext = ext || ['js'];
  
    var re = new RegExp('\\.(' + ext.join('|') + ')$');
  
    readdirSync(dir)
      .filter(ignored)
      .forEach(function(path) {
        path = join(dir, path);
        if (statSync(path).isDirectory()) {
          exports.files(path, ext, ret);
        } else if (path.match(re)) {
          ret.push(path);
        }
      });
  
    return ret;
  };
  
  /**
   * Compute a slug from the given `str`.
   *
   * @api private
   * @param {string} str
   * @return {string}
   */
  exports.slug = function(str) {
    return str
      .toLowerCase()
      .replace(/ +/g, '-')
      .replace(/[^-\w]/g, '');
  };
  
  /**
   * Strip the function definition from `str`, and re-indent for pre whitespace.
   *
   * @param {string} str
   * @return {string}
   */
  exports.clean = function(str) {
    str = str
      .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '')
      .replace(/^function *\(.*\)\s*{|\(.*\) *=> *{?/, '')
      .replace(/\s+\}$/, '');
  
    var spaces = str.match(/^\n?( *)/)[1].length;
    var tabs = str.match(/^\n?(\t*)/)[1].length;
    var re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
  
    str = str.replace(re, '');
  
    return exports.trim(str);
  };
  
  /**
   * Trim the given `str`.
   *
   * @api private
   * @param {string} str
   * @return {string}
   */
  exports.trim = function(str) {
    return str.replace(/^\s+|\s+$/g, '');
  };
  
  /**
   * Parse the given `qs`.
   *
   * @api private
   * @param {string} qs
   * @return {Object}
   */
  exports.parseQuery = function(qs) {
    return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair) {
      var i = pair.indexOf('=');
      var key = pair.slice(0, i);
      var val = pair.slice(++i);
  
      obj[key] = decodeURIComponent(val);
      return obj;
    }, {});
  };
  
  /**
   * Highlight the given string of `js`.
   *
   * @api private
   * @param {string} js
   * @return {string}
   */
  function highlight(js) {
    return js
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
      .replace(/('.*?')/gm, '<span class="string">$1</span>')
      .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
      .replace(/(\d+)/gm, '<span class="number">$1</span>')
      .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
      .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>');
  }
  
  /**
   * Highlight the contents of tag `name`.
   *
   * @api private
   * @param {string} name
   */
  exports.highlightTags = function(name) {
    var code = document.getElementById('mocha').getElementsByTagName(name);
    for (var i = 0, len = code.length; i < len; ++i) {
      code[i].innerHTML = highlight(code[i].innerHTML);
    }
  };
  
  /**
   * If a value could have properties, and has none, this function is called,
   * which returns a string representation of the empty value.
   *
   * Functions w/ no properties return `'[Function]'`
   * Arrays w/ length === 0 return `'[]'`
   * Objects w/ no properties return `'{}'`
   * All else: return result of `value.toString()`
   *
   * @api private
   * @param {*} value The value to inspect.
   * @param {string} [type] The type of the value, if known.
   * @returns {string}
   */
  function emptyRepresentation(value, type) {
    type = type || exports.type(value);
  
    switch (type) {
      case 'function':
        return '[Function]';
      case 'object':
        return '{}';
      case 'array':
        return '[]';
      default:
        return value.toString();
    }
  }
  
  /**
   * Takes some variable and asks `Object.prototype.toString()` what it thinks it
   * is.
   *
   * @api private
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
   * @param {*} value The value to test.
   * @returns {string}
   * @example
   * type({}) // 'object'
   * type([]) // 'array'
   * type(1) // 'number'
   * type(false) // 'boolean'
   * type(Infinity) // 'number'
   * type(null) // 'null'
   * type(new Date()) // 'date'
   * type(/foo/) // 'regexp'
   * type('type') // 'string'
   * type(global) // 'global'
   */
  exports.type = function type(value) {
    if (value === undefined) {
      return 'undefined';
    } else if (value === null) {
      return 'null';
    } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
      return 'buffer';
    }
    return Object.prototype.toString.call(value)
      .replace(/^\[.+\s(.+?)\]$/, '$1')
      .toLowerCase();
  };
  
  /**
   * Stringify `value`. Different behavior depending on type of value:
   *
   * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
   * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
   * - If `value` is an *empty* object, function, or array, return result of function
   *   {@link emptyRepresentation}.
   * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
   *   JSON.stringify().
   *
   * @api private
   * @see exports.type
   * @param {*} value
   * @return {string}
   */
  exports.stringify = function(value) {
    var type = exports.type(value);
  
    if (!~exports.indexOf(['object', 'array', 'function'], type)) {
      if (type !== 'buffer') {
        return jsonStringify(value);
      }
      var json = value.toJSON();
      // Based on the toJSON result
      return jsonStringify(json.data && json.type ? json.data : json, 2)
        .replace(/,(\n|$)/g, '$1');
    }
  
    for (var prop in value) {
      if (Object.prototype.hasOwnProperty.call(value, prop)) {
        return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1');
      }
    }
  
    return emptyRepresentation(value, type);
  };
  
  /**
   * like JSON.stringify but more sense.
   *
   * @api private
   * @param {Object}  object
   * @param {number=} spaces
   * @param {number=} depth
   * @returns {*}
   */
  function jsonStringify(object, spaces, depth) {
    if (typeof spaces === 'undefined') {
      // primitive types
      return _stringify(object);
    }
  
    depth = depth || 1;
    var space = spaces * depth;
    var str = isArray(object) ? '[' : '{';
    var end = isArray(object) ? ']' : '}';
    var length = object.length || exports.keys(object).length;
    // `.repeat()` polyfill
    function repeat(s, n) {
      return new Array(n).join(s);
    }
  
    function _stringify(val) {
      switch (exports.type(val)) {
        case 'null':
        case 'undefined':
          val = '[' + val + ']';
          break;
        case 'array':
        case 'object':
          val = jsonStringify(val, spaces, depth + 1);
          break;
        case 'boolean':
        case 'regexp':
        case 'number':
          val = val === 0 && (1 / val) === -Infinity // `-0`
            ? '-0'
            : val.toString();
          break;
        case 'date':
          var sDate = isNaN(val.getTime())        // Invalid date
            ? val.toString()
            : val.toISOString();
          val = '[Date: ' + sDate + ']';
          break;
        case 'buffer':
          var json = val.toJSON();
          // Based on the toJSON result
          json = json.data && json.type ? json.data : json;
          val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
          break;
        default:
          val = (val === '[Function]' || val === '[Circular]')
            ? val
            : JSON.stringify(val); // string
      }
      return val;
    }
  
    for (var i in object) {
      if (!object.hasOwnProperty(i)) {
        continue; // not my business
      }
      --length;
      str += '\n ' + repeat(' ', space)
        + (isArray(object) ? '' : '"' + i + '": ') // key
        + _stringify(object[i])                     // value
        + (length ? ',' : '');                     // comma
    }
  
    return str
      // [], {}
      + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end);
  }
  
  /**
   * Test if a value is a buffer.
   *
   * @api private
   * @param {*} value The value to test.
   * @return {boolean} True if `value` is a buffer, otherwise false
   */
  exports.isBuffer = function(value) {
    return typeof Buffer !== 'undefined' && Buffer.isBuffer(value);
  };
  
  /**
   * Return a new Thing that has the keys in sorted order. Recursive.
   *
   * If the Thing...
   * - has already been seen, return string `'[Circular]'`
   * - is `undefined`, return string `'[undefined]'`
   * - is `null`, return value `null`
   * - is some other primitive, return the value
   * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
   * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
   * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
   *
   * @api private
   * @see {@link exports.stringify}
   * @param {*} value Thing to inspect.  May or may not have properties.
   * @param {Array} [stack=[]] Stack of seen values
   * @return {(Object|Array|Function|string|undefined)}
   */
  exports.canonicalize = function(value, stack) {
    var canonicalizedObj;
    /* eslint-disable no-unused-vars */
    var prop;
    /* eslint-enable no-unused-vars */
    var type = exports.type(value);
    function withStack(value, fn) {
      stack.push(value);
      fn();
      stack.pop();
    }
  
    stack = stack || [];
  
    if (exports.indexOf(stack, value) !== -1) {
      return '[Circular]';
    }
  
    switch (type) {
      case 'undefined':
      case 'buffer':
      case 'null':
        canonicalizedObj = value;
        break;
      case 'array':
        withStack(value, function() {
          canonicalizedObj = exports.map(value, function(item) {
            return exports.canonicalize(item, stack);
          });
        });
        break;
      case 'function':
        /* eslint-disable guard-for-in */
        for (prop in value) {
          canonicalizedObj = {};
          break;
        }
        /* eslint-enable guard-for-in */
        if (!canonicalizedObj) {
          canonicalizedObj = emptyRepresentation(value, type);
          break;
        }
      /* falls through */
      case 'object':
        canonicalizedObj = canonicalizedObj || {};
        withStack(value, function() {
          exports.forEach(exports.keys(value).sort(), function(key) {
            canonicalizedObj[key] = exports.canonicalize(value[key], stack);
          });
        });
        break;
      case 'date':
      case 'number':
      case 'regexp':
      case 'boolean':
        canonicalizedObj = value;
        break;
      default:
        canonicalizedObj = value.toString();
    }
  
    return canonicalizedObj;
  };
  
  /**
   * Lookup file names at the given `path`.
   *
   * @api public
   * @param {string} path Base path to start searching from.
   * @param {string[]} extensions File extensions to look for.
   * @param {boolean} recursive Whether or not to recurse into subdirectories.
   * @return {string[]} An array of paths.
   */
  exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
    var files = [];
    var re = new RegExp('\\.(' + extensions.join('|') + ')$');
  
    if (!exists(path)) {
      if (exists(path + '.js')) {
        path += '.js';
      } else {
        files = glob.sync(path);
        if (!files.length) {
          throw new Error("cannot resolve path (or pattern) '" + path + "'");
        }
        return files;
      }
    }
  
    try {
      var stat = statSync(path);
      if (stat.isFile()) {
        return path;
      }
    } catch (err) {
      // ignore error
      return;
    }
  
    readdirSync(path).forEach(function(file) {
      file = join(path, file);
      try {
        var stat = statSync(file);
        if (stat.isDirectory()) {
          if (recursive) {
            files = files.concat(lookupFiles(file, extensions, recursive));
          }
          return;
        }
      } catch (err) {
        // ignore error
        return;
      }
      if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') {
        return;
      }
      files.push(file);
    });
  
    return files;
  };
  
  /**
   * Generate an undefined error with a message warning the user.
   *
   * @return {Error}
   */
  
  exports.undefinedError = function() {
    return new Error('Caught undefined error, did you throw without specifying what?');
  };
  
  /**
   * Generate an undefined error if `err` is not defined.
   *
   * @param {Error} err
   * @return {Error}
   */
  
  exports.getError = function(err) {
    return err || exports.undefinedError();
  };
  
  /**
   * @summary
   * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
   * @description
   * When invoking this function you get a filter function that get the Error.stack as an input,
   * and return a prettify output.
   * (i.e: strip Mocha, node_modules, bower and componentJS from stack trace).
   * @returns {Function}
   */
  exports.stackTraceFilter = function() {
    // TODO: Replace with `process.browser`
    var slash = '/';
    var is = typeof document === 'undefined' ? { node: true } : { browser: true };
    var cwd = is.node
        ? process.cwd() + slash
        : (typeof location === 'undefined' ? window.location : location).href.replace(/\/[^\/]*$/, '/');
  
    function isMochaInternal(line) {
      return (~line.indexOf('node_modules' + slash + 'mocha'))
        || (~line.indexOf('components' + slash + 'mochajs'))
        || (~line.indexOf('components' + slash + 'mocha'));
    }
  
    // node_modules, bower, componentJS
    function isBrowserModule(line) {
      return (~line.indexOf('node_modules')) || (~line.indexOf('components'));
    }
  
    function isNodeInternal(line) {
      return (~line.indexOf('(timers.js:'))
        || (~line.indexOf('(events.js:'))
        || (~line.indexOf('(node.js:'))
        || (~line.indexOf('(module.js:'))
        || (~line.indexOf('GeneratorFunctionPrototype.next (native)'))
        || false;
    }
  
    return function(stack) {
      stack = stack.split('\n');
  
      stack = exports.reduce(stack, function(list, line) {
        if (is.node && (isMochaInternal(line) || isNodeInternal(line))) {
          return list;
        }
  
        if (is.browser && (isBrowserModule(line))) {
          return list;
        }
  
        // Clean up cwd(absolute)
        list.push(line.replace(cwd, ''));
        return list;
      }, []);
  
      return stack.join('\n');
    };
  };
  