| /** |
| * Module dependencies. |
| */ |
| |
| var fs = require('fs') |
| , path = require('path') |
| , basename = path.basename |
| , exists = fs.existsSync || path.existsSync |
| , glob = require('glob') |
| , join = path.join |
| , debug = require('debug')('mocha:watch'); |
| |
| /** |
| * Ignored directories. |
| */ |
| |
| var ignore = ['node_modules', '.git']; |
| |
| /** |
| * Escape special characters in the given string of html. |
| * |
| * @param {String} html |
| * @return {String} |
| * @api private |
| */ |
| |
| exports.escape = function(html){ |
| return String(html) |
| .replace('&', '&') |
| .replace('"', '"') |
| .replace('<', '<') |
| .replace('>', '>'); |
| }; |
| |
| /** |
| * Array#forEach (<=IE8) |
| * |
| * @param {Array} array |
| * @param {Function} fn |
| * @param {Object} scope |
| * @api private |
| */ |
| |
| exports.forEach = function(arr, fn, scope){ |
| for (var i = 0, l = arr.length; i < l; i++) |
| fn.call(scope, arr[i], i); |
| }; |
| |
| /** |
| * Array#map (<=IE8) |
| * |
| * @param {Array} array |
| * @param {Function} fn |
| * @param {Object} scope |
| * @api private |
| */ |
| |
| 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) |
| * |
| * @parma {Array} arr |
| * @param {Object} obj to find index of |
| * @param {Number} start |
| * @api private |
| */ |
| |
| 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) |
| * |
| * @param {Array} array |
| * @param {Function} fn |
| * @param {Object} initial value |
| * @api private |
| */ |
| |
| 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) |
| * |
| * @param {Array} array |
| * @param {Function} fn |
| * @api private |
| */ |
| |
| 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) |
| * |
| * @param {Object} obj |
| * @return {Array} keys |
| * @api private |
| */ |
| |
| exports.keys = Object.keys || function(obj) { |
| var keys = [] |
| , 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. |
| * |
| * @param {Array} files |
| * @param {Function} fn |
| * @api private |
| */ |
| |
| exports.watch = function(files, fn){ |
| var options = { interval: 100 }; |
| files.forEach(function(file){ |
| debug('file %s', file); |
| fs.watchFile(file, options, function(curr, prev){ |
| if (prev.mtime < curr.mtime) fn(file); |
| }); |
| }); |
| }; |
| |
| /** |
| * Array.isArray (<=IE8) |
| * |
| * @param {Object} obj |
| * @return {Boolean} |
| * @api private |
| */ |
| var isArray = Array.isArray || function (obj) { |
| return '[object Array]' == {}.toString.call(obj); |
| }; |
| |
| /** |
| * @description |
| * 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. |
| */ |
| |
| function ignored(path){ |
| return !~ignore.indexOf(path); |
| } |
| |
| /** |
| * Lookup files in the given `dir`. |
| * |
| * @return {Array} |
| * @api private |
| */ |
| |
| exports.files = function(dir, ext, ret){ |
| ret = ret || []; |
| ext = ext || ['js']; |
| |
| var re = new RegExp('\\.(' + ext.join('|') + ')$'); |
| |
| fs.readdirSync(dir) |
| .filter(ignored) |
| .forEach(function(path){ |
| path = join(dir, path); |
| if (fs.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`. |
| * |
| * @param {String} str |
| * @return {String} |
| * @api private |
| */ |
| |
| exports.slug = function(str){ |
| return str |
| .toLowerCase() |
| .replace(/ +/g, '-') |
| .replace(/[^-\w]/g, ''); |
| }; |
| |
| /** |
| * Strip the function definition from `str`, |
| * and re-indent for pre whitespace. |
| */ |
| |
| exports.clean = function(str) { |
| str = str |
| .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') |
| .replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '') |
| .replace(/\s+\}$/, ''); |
| |
| var spaces = str.match(/^\n?( *)/)[1].length |
| , tabs = str.match(/^\n?(\t*)/)[1].length |
| , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); |
| |
| str = str.replace(re, ''); |
| |
| return exports.trim(str); |
| }; |
| |
| /** |
| * Trim the given `str`. |
| * |
| * @param {String} str |
| * @return {String} |
| * @api private |
| */ |
| |
| exports.trim = function(str){ |
| return str.replace(/^\s+|\s+$/g, ''); |
| }; |
| |
| /** |
| * Parse the given `qs`. |
| * |
| * @param {String} qs |
| * @return {Object} |
| * @api private |
| */ |
| |
| exports.parseQuery = function(qs){ |
| return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ |
| var i = pair.indexOf('=') |
| , key = pair.slice(0, i) |
| , val = pair.slice(++i); |
| |
| obj[key] = decodeURIComponent(val); |
| return obj; |
| }, {}); |
| }; |
| |
| /** |
| * Highlight the given string of `js`. |
| * |
| * @param {String} js |
| * @return {String} |
| * @api private |
| */ |
| |
| function highlight(js) { |
| return js |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .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`. |
| * |
| * @param {String} name |
| * @api private |
| */ |
| |
| 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()` |
| * |
| * @param {*} value Value to inspect |
| * @param {string} [type] The type of the value, if known. |
| * @returns {string} |
| */ |
| var emptyRepresentation = 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 `{}.toString()` what it thinks it is. |
| * @param {*} value Anything |
| * @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' |
| * @api private |
| * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString |
| * @returns {string} |
| */ |
| exports.type = function type(value) { |
| if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { |
| return 'buffer'; |
| } |
| return Object.prototype.toString.call(value) |
| .replace(/^\[.+\s(.+?)\]$/, '$1') |
| .toLowerCase(); |
| }; |
| |
| /** |
| * @summary Stringify `value`. |
| * @description 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(). |
| * |
| * @see exports.type |
| * @param {*} value |
| * @return {string} |
| * @api private |
| */ |
| |
| 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); |
| }; |
| |
| /** |
| * @description |
| * like JSON.stringify but more sense. |
| * @param {Object} object |
| * @param {Number=} spaces |
| * @param {number=} depth |
| * @returns {*} |
| * @private |
| */ |
| function jsonStringify(object, spaces, depth) { |
| if(typeof spaces == 'undefined') return _stringify(object); // primitive types |
| |
| depth = depth || 1; |
| var space = spaces * depth |
| , str = isArray(object) ? '[' : '{' |
| , end = isArray(object) ? ']' : '}' |
| , length = object.length || exports.keys(object).length |
| , repeat = function(s, n) { return new Array(n).join(s); }; // `.repeat()` polyfill |
| |
| 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': |
| val = '[Date: ' + val.toISOString() + ']'; |
| 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 |
| : '"' + val + '"'; //string |
| } |
| return val; |
| } |
| |
| for(var i in object) { |
| if(!Object.prototype.hasOwnProperty.call(object, 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); |
| } |
| |
| /** |
| * Return if obj is a Buffer |
| * @param {Object} arg |
| * @return {Boolean} |
| * @api private |
| */ |
| exports.isBuffer = function (arg) { |
| return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg); |
| }; |
| |
| /** |
| * @summary Return a new Thing that has the keys in sorted order. Recursive. |
| * @description 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()` |
| * |
| * @param {*} value Thing to inspect. May or may not have properties. |
| * @param {Array} [stack=[]] Stack of seen values |
| * @return {(Object|Array|Function|string|undefined)} |
| * @see {@link exports.stringify} |
| * @api private |
| */ |
| |
| exports.canonicalize = function(value, stack) { |
| var canonicalizedObj, |
| type = exports.type(value), |
| prop, |
| withStack = 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': |
| for (prop in value) { |
| canonicalizedObj = {}; |
| break; |
| } |
| 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`. |
| */ |
| 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 = fs.statSync(path); |
| if (stat.isFile()) return path; |
| } |
| catch (ignored) { |
| return; |
| } |
| |
| fs.readdirSync(path).forEach(function(file) { |
| file = join(path, file); |
| try { |
| var stat = fs.statSync(file); |
| if (stat.isDirectory()) { |
| if (recursive) { |
| files = files.concat(lookupFiles(file, extensions, recursive)); |
| } |
| return; |
| } |
| } |
| catch (ignored) { |
| 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() { |
| var slash = '/' |
| , is = typeof document === 'undefined' |
| ? { node: true } |
| : { browser: true } |
| , cwd = is.node |
| ? process.cwd() + slash |
| : location.href.replace(/\/[^\/]*$/, '/'); |
| |
| function isNodeModule (line) { |
| return (~line.indexOf('node_modules')); |
| } |
| |
| function isMochaInternal (line) { |
| return (~line.indexOf('node_modules' + slash + 'tap-mocha-reporter')) || |
| (~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('(domain.js:')) || |
| (~line.indexOf('(events.js:')) || |
| (~line.indexOf('(node.js:')) || |
| (~line.indexOf('(module.js:')) || |
| (~line.indexOf('at node.js:')) || |
| (~line.indexOf('GeneratorFunctionPrototype.next (native)')) || |
| false |
| } |
| |
| return function(stack) { |
| stack = stack.split('\n'); |
| |
| stack = stack.reduce(function (list, line) { |
| if (is.node && (isNodeModule(line) || |
| 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'); |
| } |
| }; |