better code injection handling for js

This commit is contained in:
wing328
2016-06-28 23:17:28 +08:00
parent acc28495e8
commit aec2f4e27c
1354 changed files with 313854 additions and 109 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011-2015 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,11 @@
[![Build Status](https://api.travis-ci.org/mochajs/mocha.svg?branch=master)](http://travis-ci.org/mochajs/mocha) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mochajs/mocha?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Mocha test framework](http://f.cl.ly/items/3l1k0n2A1U3M1I1L210p/Screen%20Shot%202012-02-24%20at%202.21.43%20PM.png)](http://mochajs.org)
Mocha is a simple, flexible, fun JavaScript test framework for node.js and the browser. For more information view the [documentation](http://mochajs.org).
## Links
- [Google Group](http://groups.google.com/group/mochajs)
- [Wiki](https://github.com/mochajs/mocha/wiki)
- Mocha [Extensions and reporters](https://github.com/mochajs/mocha/wiki)

View File

@@ -0,0 +1,3 @@
---
rules:
no-process-exit: 0

View File

@@ -0,0 +1,489 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander'),
path = require('path'),
fs = require('fs'),
resolve = path.resolve,
exists = fs.existsSync || path.existsSync,
Mocha = require('../'),
utils = Mocha.utils,
join = path.join,
cwd = process.cwd(),
getOptions = require('./options'),
mocha = new Mocha;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
var Date = global.Date
, setTimeout = global.setTimeout
, setInterval = global.setInterval
, clearTimeout = global.clearTimeout
, clearInterval = global.clearInterval;
/**
* Files.
*/
var files = [];
/**
* Globals.
*/
var globals = [];
/**
* Requires.
*/
var requires = [];
/**
* Images.
*/
var images = {
fail: __dirname + '/../images/error.png'
, pass: __dirname + '/../images/ok.png'
};
// options
program
.version(JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version)
.usage('[debug] [options] [files]')
.option('-A, --async-only', "force all tests to take a callback (async) or return a promise")
.option('-c, --colors', 'force enabling of colors')
.option('-C, --no-colors', 'force disabling of colors')
.option('-G, --growl', 'enable growl notification support')
.option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
.option('-R, --reporter <name>', 'specify the reporter to use', 'spec')
.option('-S, --sort', "sort test files")
.option('-b, --bail', "bail after first test failure")
.option('-d, --debug', "enable node's debugger, synonym for node --debug")
.option('-g, --grep <pattern>', 'only run tests matching <pattern>')
.option('-f, --fgrep <string>', 'only run tests containing <string>')
.option('-gc, --expose-gc', 'expose gc extension')
.option('-i, --invert', 'inverts --grep and --fgrep matches')
.option('-r, --require <name>', 'require the given module')
.option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]')
.option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]')
.option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd')
.option('-w, --watch', 'watch files for changes')
.option('--check-leaks', 'check for global variable leaks')
.option('--full-trace', 'display the full stack trace')
.option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, [])
.option('--debug-brk', "enable node's debugger breaking on the first line")
.option('--globals <names>', 'allow the given comma-delimited global [names]', list, [])
.option('--es_staging', 'enable all staged features')
.option('--harmony<_classes,_generators,...>', 'all node --harmony* flags are available')
.option('--inline-diffs', 'display actual/expected differences inline within each string')
.option('--interfaces', 'display available interfaces')
.option('--no-deprecation', 'silence deprecation warnings')
.option('--no-exit', 'require a clean shutdown of the event loop: mocha will not call process.exit')
.option('--no-timeouts', 'disables timeouts, given implicitly with --debug')
.option('--opts <path>', 'specify opts path', 'test/mocha.opts')
.option('--prof', 'log statistical profiling information')
.option('--recursive', 'include sub directories')
.option('--reporters', 'display available reporters')
.option('--throw-deprecation', 'throw an exception anytime a deprecated function is used')
.option('--trace', 'trace function calls')
.option('--trace-deprecation', 'show stack traces on deprecations')
.option('--watch-extensions <ext>,...', 'additional extensions to monitor with --watch', list, [])
.option('--delay', 'wait for async suite definition')
program.name = 'mocha';
// init command
program
.command('init <path>')
.description('initialize a client-side mocha setup at <path>')
.action(function(path){
var mkdir = require('mkdirp');
mkdir.sync(path);
var css = fs.readFileSync(join(__dirname, '..', 'mocha.css'));
var js = fs.readFileSync(join(__dirname, '..', 'mocha.js'));
var tmpl = fs.readFileSync(join(__dirname, '..', 'lib/template.html'));
fs.writeFileSync(join(path, 'mocha.css'), css);
fs.writeFileSync(join(path, 'mocha.js'), js);
fs.writeFileSync(join(path, 'tests.js'), '');
fs.writeFileSync(join(path, 'index.html'), tmpl);
process.exit(0);
});
// --globals
program.on('globals', function(val){
globals = globals.concat(list(val));
});
// --reporters
program.on('reporters', function(){
console.log();
console.log(' dot - dot matrix');
console.log(' doc - html documentation');
console.log(' spec - hierarchical spec list');
console.log(' json - single json object');
console.log(' progress - progress bar');
console.log(' list - spec-style listing');
console.log(' tap - test-anything-protocol');
console.log(' landing - unicode landing strip');
console.log(' xunit - xunit reporter');
console.log(' html-cov - HTML test coverage');
console.log(' json-cov - JSON test coverage');
console.log(' min - minimal reporter (great with --watch)');
console.log(' json-stream - newline delimited json events');
console.log(' markdown - markdown documentation (github flavour)');
console.log(' nyan - nyan cat!');
console.log();
process.exit();
});
// --interfaces
program.on('interfaces', function(){
console.log('');
console.log(' bdd');
console.log(' tdd');
console.log(' qunit');
console.log(' exports');
console.log('');
process.exit();
});
// -r, --require
module.paths.push(cwd, join(cwd, 'node_modules'));
program.on('require', function(mod){
var abs = exists(mod) || exists(mod + '.js');
if (abs) mod = resolve(mod);
requires.push(mod);
});
// load mocha.opts into process.argv
getOptions();
// parse args
program.parse(process.argv);
// infinite stack traces
Error.stackTraceLimit = Infinity; // TODO: config
// reporter options
var reporterOptions = {};
if (program.reporterOptions !== undefined) {
program.reporterOptions.split(",").forEach(function(opt) {
var L = opt.split("=");
if (L.length > 2 || L.length === 0) {
throw new Error("invalid reporter option '" + opt + "'");
} else if (L.length === 2) {
reporterOptions[L[0]] = L[1];
} else {
reporterOptions[L[0]] = true;
}
});
}
// reporter
mocha.reporter(program.reporter, reporterOptions);
// interface
mocha.ui(program.ui);
// load reporter
try {
Reporter = require('../lib/reporters/' + program.reporter);
} catch (err) {
try {
Reporter = require(program.reporter);
} catch (err) {
throw new Error('reporter "' + program.reporter + '" does not exist');
}
}
// --no-colors
if (!program.colors) mocha.useColors(false);
// --colors
if (~process.argv.indexOf('--colors') ||
~process.argv.indexOf('-c')) {
mocha.useColors(true);
}
// --inline-diffs
if (program.inlineDiffs) mocha.useInlineDiffs(true);
// --slow <ms>
if (program.slow) mocha.suite.slow(program.slow);
// --no-timeouts
if (!program.timeouts) mocha.enableTimeouts(false);
// --timeout
if (program.timeout) mocha.suite.timeout(program.timeout);
// --bail
mocha.suite.bail(program.bail);
// --grep
if (program.grep) mocha.grep(new RegExp(program.grep));
// --fgrep
if (program.fgrep) mocha.grep(program.fgrep);
// --invert
if (program.invert) mocha.invert();
// --check-leaks
if (program.checkLeaks) mocha.checkLeaks();
// --stack-trace
if(program.fullTrace) mocha.fullTrace();
// --growl
if (program.growl) mocha.growl();
// --async-only
if (program.asyncOnly) mocha.asyncOnly();
// --delay
if (program.delay) mocha.delay();
// --globals
mocha.globals(globals);
// custom compiler support
var extensions = ['js'];
program.compilers.forEach(function(c) {
var compiler = c.split(':')
, ext = compiler[0]
, mod = compiler[1];
if (mod[0] == '.') mod = join(process.cwd(), mod);
require(mod);
extensions.push(ext);
program.watchExtensions.push(ext);
});
// requires
requires.forEach(function(mod) {
require(mod);
});
//args
var args = program.args;
// default files to test/*.{js,coffee}
if (!args.length) args.push('test');
args.forEach(function(arg){
files = files.concat(utils.lookupFiles(arg, extensions, program.recursive));
});
// resolve
files = files.map(function(path){
return resolve(path);
});
if (program.sort) {
files.sort();
}
// --watch
var runner;
if (program.watch) {
console.log();
hideCursor();
process.on('SIGINT', function(){
showCursor();
console.log('\n');
process.exit();
});
var watchFiles = utils.files(cwd, [ 'js' ].concat(program.watchExtensions));
var runAgain = false;
function loadAndRun() {
try {
mocha.files = files;
runAgain = false;
runner = mocha.run(function(){
runner = null;
if (runAgain) {
rerun();
}
});
} catch(e) {
console.log(e.stack);
}
}
function purge() {
watchFiles.forEach(function(file){
delete require.cache[file];
});
}
loadAndRun();
function rerun() {
purge();
stop()
if (!program.grep)
mocha.grep(null);
mocha.suite = mocha.suite.clone();
mocha.suite.ctx = new Mocha.Context;
mocha.ui(program.ui);
loadAndRun();
}
utils.watch(watchFiles, function(){
runAgain = true;
if (runner) {
runner.abort();
} else {
rerun();
}
});
} else {
// load
mocha.files = files;
runner = mocha.run(program.exit ? exit : exitLater);
}
function exitLater(code) {
process.on('exit', function() { process.exit(code) })
}
function exit(code) {
// flush output for Node.js Windows pipe bug
// https://github.com/joyent/node/issues/6247 is just one bug example
// https://github.com/visionmedia/mocha/issues/333 has a good discussion
function done() {
if (!(draining--)) process.exit(code);
}
var draining = 0;
var streams = [process.stdout, process.stderr];
streams.forEach(function(stream){
// submit empty write request and wait for completion
draining += 1;
stream.write('', done);
});
done();
}
process.on('SIGINT', function() { runner.abort(); })
// enable growl notifications
function growl(runner, reporter) {
var notify = require('growl');
runner.on('end', function(){
var stats = reporter.stats;
if (stats.failures) {
var msg = stats.failures + ' of ' + runner.total + ' tests failed';
notify(msg, { name: 'mocha', title: 'Failed', image: images.fail });
} else {
notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
name: 'mocha'
, title: 'Passed'
, image: images.pass
});
}
});
}
/**
* Parse list.
*/
function list(str) {
return str.split(/ *, */);
}
/**
* Hide the cursor.
*/
function hideCursor(){
process.stdout.write('\u001b[?25l');
}
/**
* Show the cursor.
*/
function showCursor(){
process.stdout.write('\u001b[?25h');
}
/**
* Stop play()ing.
*/
function stop() {
process.stdout.write('\u001b[2K');
clearInterval(play.timer);
}
/**
* Play the given array of strings.
*/
function play(arr, interval) {
var len = arr.length
, interval = interval || 100
, i = 0;
play.timer = setInterval(function(){
var str = arr[i++ % len];
process.stdout.write('\u001b[0G' + str);
}, interval);
}

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env node
/**
* This tiny wrapper file checks for known node flags and appends them
* when found, before invoking the "real" _mocha(1) executable.
*/
var spawn = require('child_process').spawn,
path = require('path'),
fs = require('fs'),
args = [path.join(__dirname, '_mocha')];
process.argv.slice(2).forEach(function(arg){
var flag = arg.split('=')[0];
switch (flag) {
case '-d':
args.unshift('--debug');
args.push('--no-timeouts');
break;
case 'debug':
case '--debug':
case '--debug-brk':
args.unshift(arg);
args.push('--no-timeouts');
break;
case '-gc':
case '--expose-gc':
args.unshift('--expose-gc');
break;
case '--gc-global':
case '--es_staging':
case '--no-deprecation':
case '--prof':
case '--throw-deprecation':
case '--trace-deprecation':
case '--allow-natives-syntax':
args.unshift(arg);
break;
default:
if (0 == arg.indexOf('--harmony')) args.unshift(arg);
else if (0 == arg.indexOf('--trace')) args.unshift(arg);
else if (0 == arg.indexOf('--max-old-space-size')) args.unshift(arg);
else args.push(arg);
break;
}
});
var proc = spawn(process.execPath, args, { stdio: 'inherit' });
proc.on('exit', function (code, signal) {
process.on('exit', function(){
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}
});
});
// terminate children.
process.on('SIGINT', function () {
proc.kill('SIGINT'); // calls runner.abort()
proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
});

View File

@@ -0,0 +1,37 @@
/**
* Dependencies.
*/
var fs = require('fs');
/**
* Export `getOptions`.
*/
module.exports = getOptions;
/**
* Get options.
*/
function getOptions() {
var optsPath = process.argv.indexOf('--opts') !== -1
? process.argv[process.argv.indexOf('--opts') + 1]
: 'test/mocha.opts';
try {
var opts = fs.readFileSync(optsPath, 'utf8')
.replace(/\\\s/g, '%20')
.split(/\s/)
.filter(Boolean)
.map(function(value) {
return value.replace(/%20/g, ' ');
});
process.argv = process.argv
.slice(0, 2)
.concat(opts.concat(process.argv.slice(2)));
} catch (err) {
// ignore
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

View File

@@ -0,0 +1,3 @@
module.exports = process.env.COV
? require('./lib-cov/mocha')
: require('./lib/mocha');

View File

@@ -0,0 +1,4 @@
/* eslint-disable no-unused-vars */
module.exports = function(type) {
return function() {};
};

View File

@@ -0,0 +1,369 @@
/* See LICENSE file for terms of use */
/*
* Text diff implementation.
*
* This library supports the following APIS:
* JsDiff.diffChars: Character by character diff
* JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
* JsDiff.diffLines: Line based diff
*
* JsDiff.diffCss: Diff targeted at CSS content
*
* These methods are based on the implementation proposed in
* "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
*/
var JsDiff = (function() {
/*jshint maxparams: 5*/
function clonePath(path) {
return { newPos: path.newPos, components: path.components.slice(0) };
}
function removeEmpty(array) {
var ret = [];
for (var i = 0; i < array.length; i++) {
if (array[i]) {
ret.push(array[i]);
}
}
return ret;
}
function escapeHTML(s) {
var n = s;
n = n.replace(/&/g, '&amp;');
n = n.replace(/</g, '&lt;');
n = n.replace(/>/g, '&gt;');
n = n.replace(/"/g, '&quot;');
return n;
}
var Diff = function(ignoreWhitespace) {
this.ignoreWhitespace = ignoreWhitespace;
};
Diff.prototype = {
diff: function(oldString, newString) {
// Handle the identity case (this is due to unrolling editLength == 0
if (newString === oldString) {
return [{ value: newString }];
}
if (!newString) {
return [{ value: oldString, removed: true }];
}
if (!oldString) {
return [{ value: newString, added: true }];
}
newString = this.tokenize(newString);
oldString = this.tokenize(oldString);
var newLen = newString.length, oldLen = oldString.length;
var maxEditLength = newLen + oldLen;
var bestPath = [{ newPos: -1, components: [] }];
// Seed editLength = 0
var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
return bestPath[0].components;
}
for (var editLength = 1; editLength <= maxEditLength; editLength++) {
for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
var basePath;
var addPath = bestPath[diagonalPath-1],
removePath = bestPath[diagonalPath+1];
oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
if (addPath) {
// No one else is going to attempt to use this value, clear it
bestPath[diagonalPath-1] = undefined;
}
var canAdd = addPath && addPath.newPos+1 < newLen;
var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
if (!canAdd && !canRemove) {
bestPath[diagonalPath] = undefined;
continue;
}
// Select the diagonal that we want to branch from. We select the prior
// path whose position in the new string is the farthest from the origin
// and does not pass the bounds of the diff graph
if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
basePath = clonePath(removePath);
this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
} else {
basePath = clonePath(addPath);
basePath.newPos++;
this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
}
var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);
if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
return basePath.components;
} else {
bestPath[diagonalPath] = basePath;
}
}
}
},
pushComponent: function(components, value, added, removed) {
var last = components[components.length-1];
if (last && last.added === added && last.removed === removed) {
// We need to clone here as the component clone operation is just
// as shallow array clone
components[components.length-1] =
{value: this.join(last.value, value), added: added, removed: removed };
} else {
components.push({value: value, added: added, removed: removed });
}
},
extractCommon: function(basePath, newString, oldString, diagonalPath) {
var newLen = newString.length,
oldLen = oldString.length,
newPos = basePath.newPos,
oldPos = newPos - diagonalPath;
while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
newPos++;
oldPos++;
this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
}
basePath.newPos = newPos;
return oldPos;
},
equals: function(left, right) {
var reWhitespace = /\S/;
if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
return true;
} else {
return left === right;
}
},
join: function(left, right) {
return left + right;
},
tokenize: function(value) {
return value;
}
};
var CharDiff = new Diff();
var WordDiff = new Diff(true);
var WordWithSpaceDiff = new Diff();
WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) {
return removeEmpty(value.split(/(\s+|\b)/));
};
var CssDiff = new Diff(true);
CssDiff.tokenize = function(value) {
return removeEmpty(value.split(/([{}:;,]|\s+)/));
};
var LineDiff = new Diff();
LineDiff.tokenize = function(value) {
var retLines = [],
lines = value.split(/^/m);
for(var i = 0; i < lines.length; i++) {
var line = lines[i],
lastLine = lines[i - 1];
// Merge lines that may contain windows new lines
if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
retLines[retLines.length - 1] += '\n';
} else if (line) {
retLines.push(line);
}
}
return retLines;
};
return {
Diff: Diff,
diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },
diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },
createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
var ret = [];
ret.push('Index: ' + fileName);
ret.push('===================================================================');
ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader));
ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader));
var diff = LineDiff.diff(oldStr, newStr);
if (!diff[diff.length-1].value) {
diff.pop(); // Remove trailing newline add
}
diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier
function contextLines(lines) {
return lines.map(function(entry) { return ' ' + entry; });
}
function eofNL(curRange, i, current) {
var last = diff[diff.length-2],
isLast = i === diff.length-2,
isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);
// Figure out if this is the last line for the given file and missing NL
if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
curRange.push('\\ No newline at end of file');
}
}
var oldRangeStart = 0, newRangeStart = 0, curRange = [],
oldLine = 1, newLine = 1;
for (var i = 0; i < diff.length; i++) {
var current = diff[i],
lines = current.lines || current.value.replace(/\n$/, '').split('\n');
current.lines = lines;
if (current.added || current.removed) {
if (!oldRangeStart) {
var prev = diff[i-1];
oldRangeStart = oldLine;
newRangeStart = newLine;
if (prev) {
curRange = contextLines(prev.lines.slice(-4));
oldRangeStart -= curRange.length;
newRangeStart -= curRange.length;
}
}
curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; }));
eofNL(curRange, i, current);
if (current.added) {
newLine += lines.length;
} else {
oldLine += lines.length;
}
} else {
if (oldRangeStart) {
// Close out any changes that have been output (or join overlapping)
if (lines.length <= 8 && i < diff.length-2) {
// Overlapping
curRange.push.apply(curRange, contextLines(lines));
} else {
// end the range and output
var contextSize = Math.min(lines.length, 4);
ret.push(
'@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize)
+ ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize)
+ ' @@');
ret.push.apply(ret, curRange);
ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
if (lines.length <= 4) {
eofNL(ret, i, current);
}
oldRangeStart = 0; newRangeStart = 0; curRange = [];
}
}
oldLine += lines.length;
newLine += lines.length;
}
}
return ret.join('\n') + '\n';
},
applyPatch: function(oldStr, uniDiff) {
var diffstr = uniDiff.split('\n');
var diff = [];
var remEOFNL = false,
addEOFNL = false;
for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) {
if(diffstr[i][0] === '@') {
var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
diff.unshift({
start:meh[3],
oldlength:meh[2],
oldlines:[],
newlength:meh[4],
newlines:[]
});
} else if(diffstr[i][0] === '+') {
diff[0].newlines.push(diffstr[i].substr(1));
} else if(diffstr[i][0] === '-') {
diff[0].oldlines.push(diffstr[i].substr(1));
} else if(diffstr[i][0] === ' ') {
diff[0].newlines.push(diffstr[i].substr(1));
diff[0].oldlines.push(diffstr[i].substr(1));
} else if(diffstr[i][0] === '\\') {
if (diffstr[i-1][0] === '+') {
remEOFNL = true;
} else if(diffstr[i-1][0] === '-') {
addEOFNL = true;
}
}
}
var str = oldStr.split('\n');
for (var i = diff.length - 1; i >= 0; i--) {
var d = diff[i];
for (var j = 0; j < d.oldlength; j++) {
if(str[d.start-1+j] !== d.oldlines[j]) {
return false;
}
}
Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
}
if (remEOFNL) {
while (!str[str.length-1]) {
str.pop();
}
} else if (addEOFNL) {
str.push('');
}
return str.join('\n');
},
convertChangesToXML: function(changes){
var ret = [];
for ( var i = 0; i < changes.length; i++) {
var change = changes[i];
if (change.added) {
ret.push('<ins>');
} else if (change.removed) {
ret.push('<del>');
}
ret.push(escapeHTML(change.value));
if (change.added) {
ret.push('</ins>');
} else if (change.removed) {
ret.push('</del>');
}
}
return ret.join('');
},
// See: http://code.google.com/p/google-diff-match-patch/wiki/API
convertChangesToDMP: function(changes){
var ret = [], change;
for ( var i = 0; i < changes.length; i++) {
change = changes[i];
ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
}
return ret;
}
};
})();
if (typeof module !== 'undefined') {
module.exports = JsDiff;
}

View File

@@ -0,0 +1,193 @@
/**
* Module exports.
*/
exports.EventEmitter = EventEmitter;
/**
* Object#hasOwnProperty reference.
*/
var objToString = Object.prototype.toString;
/**
* Check if a value is an array.
*
* @api private
* @param {*} val The value to test.
* @return {boolean} true if the value is a boolean, otherwise false.
*/
function isArray(val) {
return objToString.call(val) === '[object Array]';
}
/**
* Event emitter constructor.
*
* @api public
*/
function EventEmitter() {}
/**
* Add a listener.
*
* @api public
* @param {string} name Event name.
* @param {Function} fn Event handler.
* @return {EventEmitter} Emitter instance.
*/
EventEmitter.prototype.on = function(name, fn) {
if (!this.$events) {
this.$events = {};
}
if (!this.$events[name]) {
this.$events[name] = fn;
} else if (isArray(this.$events[name])) {
this.$events[name].push(fn);
} else {
this.$events[name] = [this.$events[name], fn];
}
return this;
};
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
/**
* Adds a volatile listener.
*
* @api public
* @param {string} name Event name.
* @param {Function} fn Event handler.
* @return {EventEmitter} Emitter instance.
*/
EventEmitter.prototype.once = function(name, fn) {
var self = this;
function on() {
self.removeListener(name, on);
fn.apply(this, arguments);
}
on.listener = fn;
this.on(name, on);
return this;
};
/**
* Remove a listener.
*
* @api public
* @param {string} name Event name.
* @param {Function} fn Event handler.
* @return {EventEmitter} Emitter instance.
*/
EventEmitter.prototype.removeListener = function(name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else if (list === fn || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
return this;
};
/**
* Remove all listeners for an event.
*
* @api public
* @param {string} name Event name.
* @return {EventEmitter} Emitter instance.
*/
EventEmitter.prototype.removeAllListeners = function(name) {
if (name === undefined) {
this.$events = {};
return this;
}
if (this.$events && this.$events[name]) {
this.$events[name] = null;
}
return this;
};
/**
* Get all listeners for a given event.
*
* @api public
* @param {string} name Event name.
* @return {EventEmitter} Emitter instance.
*/
EventEmitter.prototype.listeners = function(name) {
if (!this.$events) {
this.$events = {};
}
if (!this.$events[name]) {
this.$events[name] = [];
}
if (!isArray(this.$events[name])) {
this.$events[name] = [this.$events[name]];
}
return this.$events[name];
};
/**
* Emit an event.
*
* @api public
* @param {string} name Event name.
* @return {boolean} true if at least one handler was invoked, else false.
*/
EventEmitter.prototype.emit = function(name) {
if (!this.$events) {
return false;
}
var handler = this.$events[name];
if (!handler) {
return false;
}
var args = Array.prototype.slice.call(arguments, 1);
if (typeof handler === 'function') {
handler.apply(this, args);
} else if (isArray(handler)) {
var listeners = handler.slice();
for (var i = 0, l = listeners.length; i < l; i++) {
listeners[i].apply(this, args);
}
} else {
return false;
}
return true;
};

View File

@@ -0,0 +1,117 @@
/**
* Expose `Progress`.
*/
module.exports = Progress;
/**
* Initialize a new `Progress` indicator.
*/
function Progress() {
this.percent = 0;
this.size(0);
this.fontSize(11);
this.font('helvetica, arial, sans-serif');
}
/**
* Set progress size to `size`.
*
* @api public
* @param {number} size
* @return {Progress} Progress instance.
*/
Progress.prototype.size = function(size) {
this._size = size;
return this;
};
/**
* Set text to `text`.
*
* @api public
* @param {string} text
* @return {Progress} Progress instance.
*/
Progress.prototype.text = function(text) {
this._text = text;
return this;
};
/**
* Set font size to `size`.
*
* @api public
* @param {number} size
* @return {Progress} Progress instance.
*/
Progress.prototype.fontSize = function(size) {
this._fontSize = size;
return this;
};
/**
* Set font to `family`.
*
* @param {string} family
* @return {Progress} Progress instance.
*/
Progress.prototype.font = function(family) {
this._font = family;
return this;
};
/**
* Update percentage to `n`.
*
* @param {number} n
* @return {Progress} Progress instance.
*/
Progress.prototype.update = function(n) {
this.percent = n;
return this;
};
/**
* Draw on `ctx`.
*
* @param {CanvasRenderingContext2d} ctx
* @return {Progress} Progress instance.
*/
Progress.prototype.draw = function(ctx) {
try {
var percent = Math.min(this.percent, 100);
var size = this._size;
var half = size / 2;
var x = half;
var y = half;
var rad = half - 1;
var fontSize = this._fontSize;
ctx.font = fontSize + 'px ' + this._font;
var angle = Math.PI * 2 * (percent / 100);
ctx.clearRect(0, 0, size, size);
// outer circle
ctx.strokeStyle = '#9f9f9f';
ctx.beginPath();
ctx.arc(x, y, rad, 0, angle, false);
ctx.stroke();
// inner circle
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.arc(x, y, rad - 1, 0, angle, true);
ctx.stroke();
// text
var text = this._text || (percent | 0) + '%';
var w = ctx.measureText(text).width;
ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1);
} catch (err) {
// don't fail if we can't render progress
}
return this;
};

View File

@@ -0,0 +1,11 @@
exports.isatty = function isatty() {
return true;
};
exports.getWindowSize = function getWindowSize() {
if ('innerHeight' in global) {
return [global.innerHeight, global.innerWidth];
}
// In a Web Worker, the DOM Window is not available.
return [640, 480];
};

View File

@@ -0,0 +1,89 @@
/**
* Expose `Context`.
*/
module.exports = Context;
/**
* Initialize a new `Context`.
*
* @api private
*/
function Context() {}
/**
* Set or get the context `Runnable` to `runnable`.
*
* @api private
* @param {Runnable} runnable
* @return {Context}
*/
Context.prototype.runnable = function(runnable) {
if (!arguments.length) {
return this._runnable;
}
this.test = this._runnable = runnable;
return this;
};
/**
* Set test timeout `ms`.
*
* @api private
* @param {number} ms
* @return {Context} self
*/
Context.prototype.timeout = function(ms) {
if (!arguments.length) {
return this.runnable().timeout();
}
this.runnable().timeout(ms);
return this;
};
/**
* Set test timeout `enabled`.
*
* @api private
* @param {boolean} enabled
* @return {Context} self
*/
Context.prototype.enableTimeouts = function(enabled) {
this.runnable().enableTimeouts(enabled);
return this;
};
/**
* Set test slowness threshold `ms`.
*
* @api private
* @param {number} ms
* @return {Context} self
*/
Context.prototype.slow = function(ms) {
this.runnable().slow(ms);
return this;
};
/**
* Mark a test as skipped.
*
* @api private
* @return {Context} self
*/
Context.prototype.skip = function() {
this.runnable().skip();
return this;
};
/**
* Inspect the context void of `._runnable`.
*
* @api private
* @return {string}
*/
Context.prototype.inspect = function() {
return JSON.stringify(this, function(key, val) {
return key === 'runnable' || key === 'test' ? undefined : val;
}, 2);
};

View File

@@ -0,0 +1,46 @@
/**
* Module dependencies.
*/
var Runnable = require('./runnable');
var inherits = require('./utils').inherits;
/**
* Expose `Hook`.
*/
module.exports = Hook;
/**
* Initialize a new `Hook` with the given `title` and callback `fn`.
*
* @param {String} title
* @param {Function} fn
* @api private
*/
function Hook(title, fn) {
Runnable.call(this, title, fn);
this.type = 'hook';
}
/**
* Inherit from `Runnable.prototype`.
*/
inherits(Hook, Runnable);
/**
* Get or set the test `err`.
*
* @param {Error} err
* @return {Error}
* @api public
*/
Hook.prototype.error = function(err) {
if (!arguments.length) {
err = this._error;
this._error = null;
return err;
}
this._error = err;
};

View File

@@ -0,0 +1,110 @@
/**
* Module dependencies.
*/
var Suite = require('../suite');
var Test = require('../test');
var escapeRe = require('escape-string-regexp');
/**
* BDD-style interface:
*
* describe('Array', function() {
* describe('#indexOf()', function() {
* it('should return -1 when not present', function() {
* // ...
* });
*
* it('should return the index when present', function() {
* // ...
* });
* });
* });
*
* @param {Suite} suite Root suite.
*/
module.exports = function(suite) {
var suites = [suite];
suite.on('pre-require', function(context, file, mocha) {
var common = require('./common')(suites, context);
context.before = common.before;
context.after = common.after;
context.beforeEach = common.beforeEach;
context.afterEach = common.afterEach;
context.run = mocha.options.delay && common.runWithSuite(suite);
/**
* Describe a "suite" with the given `title`
* and callback `fn` containing nested suites
* and/or tests.
*/
context.describe = context.context = function(title, fn) {
var suite = Suite.create(suites[0], title);
suite.file = file;
suites.unshift(suite);
fn.call(suite);
suites.shift();
return suite;
};
/**
* Pending describe.
*/
context.xdescribe = context.xcontext = context.describe.skip = function(title, fn) {
var suite = Suite.create(suites[0], title);
suite.pending = true;
suites.unshift(suite);
fn.call(suite);
suites.shift();
};
/**
* Exclusive suite.
*/
context.describe.only = function(title, fn) {
var suite = context.describe(title, fn);
mocha.grep(suite.fullTitle());
return suite;
};
/**
* Describe a specification or test-case
* with the given `title` and callback `fn`
* acting as a thunk.
*/
context.it = context.specify = function(title, fn) {
var suite = suites[0];
if (suite.pending) {
fn = null;
}
var test = new Test(title, fn);
test.file = file;
suite.addTest(test);
return test;
};
/**
* Exclusive test-case.
*/
context.it.only = function(title, fn) {
var test = context.it(title, fn);
var reString = '^' + escapeRe(test.fullTitle()) + '$';
mocha.grep(new RegExp(reString));
return test;
};
/**
* Pending test case.
*/
context.xit = context.xspecify = context.it.skip = function(title) {
context.it(title);
};
});
};

View File

@@ -0,0 +1,76 @@
'use strict';
/**
* Functions common to more than one interface.
*
* @param {Suite[]} suites
* @param {Context} context
* @return {Object} An object containing common functions.
*/
module.exports = function(suites, context) {
return {
/**
* This is only present if flag --delay is passed into Mocha. It triggers
* root suite execution.
*
* @param {Suite} suite The root wuite.
* @return {Function} A function which runs the root suite
*/
runWithSuite: function runWithSuite(suite) {
return function run() {
suite.run();
};
},
/**
* Execute before running tests.
*
* @param {string} name
* @param {Function} fn
*/
before: function(name, fn) {
suites[0].beforeAll(name, fn);
},
/**
* Execute after running tests.
*
* @param {string} name
* @param {Function} fn
*/
after: function(name, fn) {
suites[0].afterAll(name, fn);
},
/**
* Execute before each test case.
*
* @param {string} name
* @param {Function} fn
*/
beforeEach: function(name, fn) {
suites[0].beforeEach(name, fn);
},
/**
* Execute after each test case.
*
* @param {string} name
* @param {Function} fn
*/
afterEach: function(name, fn) {
suites[0].afterEach(name, fn);
},
test: {
/**
* Pending test case.
*
* @param {string} title
*/
skip: function(title) {
context.test(title);
}
}
};
};

View File

@@ -0,0 +1,61 @@
/**
* Module dependencies.
*/
var Suite = require('../suite');
var Test = require('../test');
/**
* TDD-style interface:
*
* exports.Array = {
* '#indexOf()': {
* 'should return -1 when the value is not present': function() {
*
* },
*
* 'should return the correct index when the value is present': function() {
*
* }
* }
* };
*
* @param {Suite} suite Root suite.
*/
module.exports = function(suite) {
var suites = [suite];
suite.on('require', visit);
function visit(obj, file) {
var suite;
for (var key in obj) {
if (typeof obj[key] === 'function') {
var fn = obj[key];
switch (key) {
case 'before':
suites[0].beforeAll(fn);
break;
case 'after':
suites[0].afterAll(fn);
break;
case 'beforeEach':
suites[0].beforeEach(fn);
break;
case 'afterEach':
suites[0].afterEach(fn);
break;
default:
var test = new Test(key, fn);
test.file = file;
suites[0].addTest(test);
}
} else {
suite = Suite.create(suites[0], key);
suites.unshift(suite);
visit(obj[key]);
suites.shift();
}
}
}
};

View File

@@ -0,0 +1,4 @@
exports.bdd = require('./bdd');
exports.tdd = require('./tdd');
exports.qunit = require('./qunit');
exports.exports = require('./exports');

View File

@@ -0,0 +1,93 @@
/**
* Module dependencies.
*/
var Suite = require('../suite');
var Test = require('../test');
var escapeRe = require('escape-string-regexp');
/**
* QUnit-style interface:
*
* suite('Array');
*
* test('#length', function() {
* var arr = [1,2,3];
* ok(arr.length == 3);
* });
*
* test('#indexOf()', function() {
* var arr = [1,2,3];
* ok(arr.indexOf(1) == 0);
* ok(arr.indexOf(2) == 1);
* ok(arr.indexOf(3) == 2);
* });
*
* suite('String');
*
* test('#length', function() {
* ok('foo'.length == 3);
* });
*
* @param {Suite} suite Root suite.
*/
module.exports = function(suite) {
var suites = [suite];
suite.on('pre-require', function(context, file, mocha) {
var common = require('./common')(suites, context);
context.before = common.before;
context.after = common.after;
context.beforeEach = common.beforeEach;
context.afterEach = common.afterEach;
context.run = mocha.options.delay && common.runWithSuite(suite);
/**
* Describe a "suite" with the given `title`.
*/
context.suite = function(title) {
if (suites.length > 1) {
suites.shift();
}
var suite = Suite.create(suites[0], title);
suite.file = file;
suites.unshift(suite);
return suite;
};
/**
* Exclusive test-case.
*/
context.suite.only = function(title, fn) {
var suite = context.suite(title, fn);
mocha.grep(suite.fullTitle());
};
/**
* Describe a specification or test-case
* with the given `title` and callback `fn`
* acting as a thunk.
*/
context.test = function(title, fn) {
var test = new Test(title, fn);
test.file = file;
suites[0].addTest(test);
return test;
};
/**
* Exclusive test-case.
*/
context.test.only = function(title, fn) {
var test = context.test(title, fn);
var reString = '^' + escapeRe(test.fullTitle()) + '$';
mocha.grep(new RegExp(reString));
};
context.test.skip = common.test.skip;
});
};

View File

@@ -0,0 +1,105 @@
/**
* Module dependencies.
*/
var Suite = require('../suite');
var Test = require('../test');
var escapeRe = require('escape-string-regexp');
/**
* TDD-style interface:
*
* suite('Array', function() {
* suite('#indexOf()', function() {
* suiteSetup(function() {
*
* });
*
* test('should return -1 when not present', function() {
*
* });
*
* test('should return the index when present', function() {
*
* });
*
* suiteTeardown(function() {
*
* });
* });
* });
*
* @param {Suite} suite Root suite.
*/
module.exports = function(suite) {
var suites = [suite];
suite.on('pre-require', function(context, file, mocha) {
var common = require('./common')(suites, context);
context.setup = common.beforeEach;
context.teardown = common.afterEach;
context.suiteSetup = common.before;
context.suiteTeardown = common.after;
context.run = mocha.options.delay && common.runWithSuite(suite);
/**
* Describe a "suite" with the given `title` and callback `fn` containing
* nested suites and/or tests.
*/
context.suite = function(title, fn) {
var suite = Suite.create(suites[0], title);
suite.file = file;
suites.unshift(suite);
fn.call(suite);
suites.shift();
return suite;
};
/**
* Pending suite.
*/
context.suite.skip = function(title, fn) {
var suite = Suite.create(suites[0], title);
suite.pending = true;
suites.unshift(suite);
fn.call(suite);
suites.shift();
};
/**
* Exclusive test-case.
*/
context.suite.only = function(title, fn) {
var suite = context.suite(title, fn);
mocha.grep(suite.fullTitle());
};
/**
* Describe a specification or test-case with the given `title` and
* callback `fn` acting as a thunk.
*/
context.test = function(title, fn) {
var suite = suites[0];
if (suite.pending) {
fn = null;
}
var test = new Test(title, fn);
test.file = file;
suite.addTest(test);
return test;
};
/**
* Exclusive test-case.
*/
context.test.only = function(title, fn) {
var test = context.test(title, fn);
var reString = '^' + escapeRe(test.fullTitle()) + '$';
mocha.grep(new RegExp(reString));
};
context.test.skip = common.test.skip;
});
};

View File

@@ -0,0 +1,487 @@
/*!
* mocha
* Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var escapeRe = require('escape-string-regexp');
var path = require('path');
var reporters = require('./reporters');
var utils = require('./utils');
/**
* Expose `Mocha`.
*/
exports = module.exports = Mocha;
/**
* To require local UIs and reporters when running in node.
*/
if (!process.browser) {
var cwd = process.cwd();
module.paths.push(cwd, path.join(cwd, 'node_modules'));
}
/**
* Expose internals.
*/
exports.utils = utils;
exports.interfaces = require('./interfaces');
exports.reporters = reporters;
exports.Runnable = require('./runnable');
exports.Context = require('./context');
exports.Runner = require('./runner');
exports.Suite = require('./suite');
exports.Hook = require('./hook');
exports.Test = require('./test');
/**
* Return image `name` path.
*
* @api private
* @param {string} name
* @return {string}
*/
function image(name) {
return path.join(__dirname, '../images', name + '.png');
}
/**
* Set up mocha with `options`.
*
* Options:
*
* - `ui` name "bdd", "tdd", "exports" etc
* - `reporter` reporter instance, defaults to `mocha.reporters.spec`
* - `globals` array of accepted globals
* - `timeout` timeout in milliseconds
* - `bail` bail on the first test failure
* - `slow` milliseconds to wait before considering a test slow
* - `ignoreLeaks` ignore global leaks
* - `fullTrace` display the full stack-trace on failing
* - `grep` string or regexp to filter tests with
*
* @param {Object} options
* @api public
*/
function Mocha(options) {
options = options || {};
this.files = [];
this.options = options;
if (options.grep) {
this.grep(new RegExp(options.grep));
}
if (options.fgrep) {
this.grep(options.fgrep);
}
this.suite = new exports.Suite('', new exports.Context());
this.ui(options.ui);
this.bail(options.bail);
this.reporter(options.reporter, options.reporterOptions);
if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
this.timeout(options.timeout);
}
this.useColors(options.useColors);
if (options.enableTimeouts !== null) {
this.enableTimeouts(options.enableTimeouts);
}
if (options.slow) {
this.slow(options.slow);
}
this.suite.on('pre-require', function(context) {
exports.afterEach = context.afterEach || context.teardown;
exports.after = context.after || context.suiteTeardown;
exports.beforeEach = context.beforeEach || context.setup;
exports.before = context.before || context.suiteSetup;
exports.describe = context.describe || context.suite;
exports.it = context.it || context.test;
exports.setup = context.setup || context.beforeEach;
exports.suiteSetup = context.suiteSetup || context.before;
exports.suiteTeardown = context.suiteTeardown || context.after;
exports.suite = context.suite || context.describe;
exports.teardown = context.teardown || context.afterEach;
exports.test = context.test || context.it;
exports.run = context.run;
});
}
/**
* Enable or disable bailing on the first failure.
*
* @api public
* @param {boolean} [bail]
*/
Mocha.prototype.bail = function(bail) {
if (!arguments.length) {
bail = true;
}
this.suite.bail(bail);
return this;
};
/**
* Add test `file`.
*
* @api public
* @param {string} file
*/
Mocha.prototype.addFile = function(file) {
this.files.push(file);
return this;
};
/**
* Set reporter to `reporter`, defaults to "spec".
*
* @param {String|Function} reporter name or constructor
* @param {Object} reporterOptions optional options
* @api public
* @param {string|Function} reporter name or constructor
* @param {Object} reporterOptions optional options
*/
Mocha.prototype.reporter = function(reporter, reporterOptions) {
if (typeof reporter === 'function') {
this._reporter = reporter;
} else {
reporter = reporter || 'spec';
var _reporter;
// Try to load a built-in reporter.
if (reporters[reporter]) {
_reporter = reporters[reporter];
}
// Try to load reporters from process.cwd() and node_modules
if (!_reporter) {
try {
_reporter = require(reporter);
} catch (err) {
err.message.indexOf('Cannot find module') !== -1
? console.warn('"' + reporter + '" reporter not found')
: console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack);
}
}
if (!_reporter && reporter === 'teamcity') {
console.warn('The Teamcity reporter was moved to a package named '
+ 'mocha-teamcity-reporter '
+ '(https://npmjs.org/package/mocha-teamcity-reporter).');
}
if (!_reporter) {
throw new Error('invalid reporter "' + reporter + '"');
}
this._reporter = _reporter;
}
this.options.reporterOptions = reporterOptions;
return this;
};
/**
* Set test UI `name`, defaults to "bdd".
*
* @api public
* @param {string} bdd
*/
Mocha.prototype.ui = function(name) {
name = name || 'bdd';
this._ui = exports.interfaces[name];
if (!this._ui) {
try {
this._ui = require(name);
} catch (err) {
throw new Error('invalid interface "' + name + '"');
}
}
this._ui = this._ui(this.suite);
return this;
};
/**
* Load registered files.
*
* @api private
*/
Mocha.prototype.loadFiles = function(fn) {
var self = this;
var suite = this.suite;
var pending = this.files.length;
this.files.forEach(function(file) {
file = path.resolve(file);
suite.emit('pre-require', global, file, self);
suite.emit('require', require(file), file, self);
suite.emit('post-require', global, file, self);
--pending || (fn && fn());
});
};
/**
* Enable growl support.
*
* @api private
*/
Mocha.prototype._growl = function(runner, reporter) {
var notify = require('growl');
runner.on('end', function() {
var stats = reporter.stats;
if (stats.failures) {
var msg = stats.failures + ' of ' + runner.total + ' tests failed';
notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });
} else {
notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
name: 'mocha',
title: 'Passed',
image: image('ok')
});
}
});
};
/**
* Add regexp to grep, if `re` is a string it is escaped.
*
* @param {RegExp|String} re
* @return {Mocha}
* @api public
* @param {RegExp|string} re
* @return {Mocha}
*/
Mocha.prototype.grep = function(re) {
this.options.grep = typeof re === 'string' ? new RegExp(escapeRe(re)) : re;
return this;
};
/**
* Invert `.grep()` matches.
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.invert = function() {
this.options.invert = true;
return this;
};
/**
* Ignore global leaks.
*
* @param {Boolean} ignore
* @return {Mocha}
* @api public
* @param {boolean} ignore
* @return {Mocha}
*/
Mocha.prototype.ignoreLeaks = function(ignore) {
this.options.ignoreLeaks = Boolean(ignore);
return this;
};
/**
* Enable global leak checking.
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.checkLeaks = function() {
this.options.ignoreLeaks = false;
return this;
};
/**
* Display long stack-trace on failing
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.fullTrace = function() {
this.options.fullStackTrace = true;
return this;
};
/**
* Enable growl support.
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.growl = function() {
this.options.growl = true;
return this;
};
/**
* Ignore `globals` array or string.
*
* @param {Array|String} globals
* @return {Mocha}
* @api public
* @param {Array|string} globals
* @return {Mocha}
*/
Mocha.prototype.globals = function(globals) {
this.options.globals = (this.options.globals || []).concat(globals);
return this;
};
/**
* Emit color output.
*
* @param {Boolean} colors
* @return {Mocha}
* @api public
* @param {boolean} colors
* @return {Mocha}
*/
Mocha.prototype.useColors = function(colors) {
if (colors !== undefined) {
this.options.useColors = colors;
}
return this;
};
/**
* Use inline diffs rather than +/-.
*
* @param {Boolean} inlineDiffs
* @return {Mocha}
* @api public
* @param {boolean} inlineDiffs
* @return {Mocha}
*/
Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs;
return this;
};
/**
* Set the timeout in milliseconds.
*
* @param {Number} timeout
* @return {Mocha}
* @api public
* @param {number} timeout
* @return {Mocha}
*/
Mocha.prototype.timeout = function(timeout) {
this.suite.timeout(timeout);
return this;
};
/**
* Set slowness threshold in milliseconds.
*
* @param {Number} slow
* @return {Mocha}
* @api public
* @param {number} slow
* @return {Mocha}
*/
Mocha.prototype.slow = function(slow) {
this.suite.slow(slow);
return this;
};
/**
* Enable timeouts.
*
* @param {Boolean} enabled
* @return {Mocha}
* @api public
* @param {boolean} enabled
* @return {Mocha}
*/
Mocha.prototype.enableTimeouts = function(enabled) {
this.suite.enableTimeouts(arguments.length && enabled !== undefined ? enabled : true);
return this;
};
/**
* Makes all tests async (accepting a callback)
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.asyncOnly = function() {
this.options.asyncOnly = true;
return this;
};
/**
* Disable syntax highlighting (in browser).
*
* @api public
*/
Mocha.prototype.noHighlighting = function() {
this.options.noHighlighting = true;
return this;
};
/**
* Enable uncaught errors to propagate (in browser).
*
* @return {Mocha}
* @api public
*/
Mocha.prototype.allowUncaught = function() {
this.options.allowUncaught = true;
return this;
};
/**
* Delay root suite execution.
* @returns {Mocha}
*/
Mocha.prototype.delay = function delay() {
this.options.delay = true;
return this;
};
/**
* Run tests and invoke `fn()` when complete.
*
* @api public
* @param {Function} fn
* @return {Runner}
*/
Mocha.prototype.run = function(fn) {
if (this.files.length) {
this.loadFiles();
}
var suite = this.suite;
var options = this.options;
options.files = this.files;
var runner = new exports.Runner(suite, options.delay);
var reporter = new this._reporter(runner, options);
runner.ignoreLeaks = options.ignoreLeaks !== false;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
runner.allowUncaught = options.allowUncaught;
if (options.grep) {
runner.grep(options.grep, options.invert);
}
if (options.globals) {
runner.globals(options.globals);
}
if (options.growl) {
this._growl(runner, reporter);
}
if (options.useColors !== undefined) {
exports.reporters.Base.useColors = options.useColors;
}
exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
function done(failures) {
if (reporter.done) {
reporter.done(failures, fn);
} else {
fn && fn(failures);
}
}
return runner.run(done);
};

View File

@@ -0,0 +1,128 @@
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @api public
* @param {string|number} val
* @param {Object} options
* @return {string|number}
*/
module.exports = function(val, options) {
options = options || {};
if (typeof val === 'string') {
return parse(val);
}
// https://github.com/mochajs/mocha/pull/1035
return options['long'] ? longFormat(val) : shortFormat(val);
};
/**
* Parse the given `str` and return milliseconds.
*
* @api private
* @param {string} str
* @return {number}
*/
function parse(str) {
var match = (/^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i).exec(str);
if (!match) {
return;
}
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'y':
return n * y;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 's':
return n * s;
case 'ms':
return n;
default:
// No default case
}
}
/**
* Short format for `ms`.
*
* @api private
* @param {number} ms
* @return {string}
*/
function shortFormat(ms) {
if (ms >= d) {
return Math.round(ms / d) + 'd';
}
if (ms >= h) {
return Math.round(ms / h) + 'h';
}
if (ms >= m) {
return Math.round(ms / m) + 'm';
}
if (ms >= s) {
return Math.round(ms / s) + 's';
}
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @api private
* @param {number} ms
* @return {string}
*/
function longFormat(ms) {
return plural(ms, d, 'day')
|| plural(ms, h, 'hour')
|| plural(ms, m, 'minute')
|| plural(ms, s, 'second')
|| ms + ' ms';
}
/**
* Pluralization helper.
*
* @api private
* @param {number} ms
* @param {number} n
* @param {string} name
*/
function plural(ms, n, name) {
if (ms < n) {
return;
}
if (ms < n * 1.5) {
return Math.floor(ms / n) + ' ' + name;
}
return Math.ceil(ms / n) + ' ' + name + 's';
}

View File

@@ -0,0 +1,15 @@
/**
* Expose `Pending`.
*/
module.exports = Pending;
/**
* Initialize a new `Pending` error with the given message.
*
* @param {string} message
*/
function Pending(message) {
this.message = message;
}

View File

@@ -0,0 +1,487 @@
/**
* Module dependencies.
*/
var tty = require('tty');
var diff = require('diff');
var ms = require('../ms');
var utils = require('../utils');
var supportsColor = process.browser ? null : require('supports-color');
/**
* Expose `Base`.
*/
exports = module.exports = Base;
/**
* Save timer references to avoid Sinon interfering.
* See: https://github.com/mochajs/mocha/issues/237
*/
/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */
/**
* Check if both stdio streams are associated with a tty.
*/
var isatty = tty.isatty(1) && tty.isatty(2);
/**
* Enable coloring by default, except in the browser interface.
*/
exports.useColors = !process.browser && (supportsColor || (process.env.MOCHA_COLORS !== undefined));
/**
* Inline diffs instead of +/-
*/
exports.inlineDiffs = false;
/**
* Default color map.
*/
exports.colors = {
pass: 90,
fail: 31,
'bright pass': 92,
'bright fail': 91,
'bright yellow': 93,
pending: 36,
suite: 0,
'error title': 0,
'error message': 31,
'error stack': 90,
checkmark: 32,
fast: 90,
medium: 33,
slow: 31,
green: 32,
light: 90,
'diff gutter': 90,
'diff added': 32,
'diff removed': 31
};
/**
* Default symbol map.
*/
exports.symbols = {
ok: '✓',
err: '✖',
dot: ''
};
// With node.js on Windows: use symbols available in terminal default fonts
if (process.platform === 'win32') {
exports.symbols.ok = '\u221A';
exports.symbols.err = '\u00D7';
exports.symbols.dot = '.';
}
/**
* Color `str` with the given `type`,
* allowing colors to be disabled,
* as well as user-defined color
* schemes.
*
* @param {string} type
* @param {string} str
* @return {string}
* @api private
*/
var color = exports.color = function(type, str) {
if (!exports.useColors) {
return String(str);
}
return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
};
/**
* Expose term window size, with some defaults for when stderr is not a tty.
*/
exports.window = {
width: 75
};
if (isatty) {
exports.window.width = process.stdout.getWindowSize
? process.stdout.getWindowSize(1)[0]
: tty.getWindowSize()[1];
}
/**
* Expose some basic cursor interactions that are common among reporters.
*/
exports.cursor = {
hide: function() {
isatty && process.stdout.write('\u001b[?25l');
},
show: function() {
isatty && process.stdout.write('\u001b[?25h');
},
deleteLine: function() {
isatty && process.stdout.write('\u001b[2K');
},
beginningOfLine: function() {
isatty && process.stdout.write('\u001b[0G');
},
CR: function() {
if (isatty) {
exports.cursor.deleteLine();
exports.cursor.beginningOfLine();
} else {
process.stdout.write('\r');
}
}
};
/**
* Outut the given `failures` as a list.
*
* @param {Array} failures
* @api public
*/
exports.list = function(failures) {
console.log();
failures.forEach(function(test, i) {
// format
var fmt = color('error title', ' %s) %s:\n')
+ color('error message', ' %s')
+ color('error stack', '\n%s\n');
// msg
var msg;
var err = test.err;
var message;
if (err.message) {
message = err.message;
} else if (typeof err.inspect === 'function') {
message = err.inspect() + '';
} else {
message = '';
}
var stack = err.stack || message;
var index = stack.indexOf(message);
var actual = err.actual;
var expected = err.expected;
var escape = true;
if (index === -1) {
msg = message;
} else {
index += message.length;
msg = stack.slice(0, index);
// remove msg from stack
stack = stack.slice(index + 1);
}
// uncaught
if (err.uncaught) {
msg = 'Uncaught ' + msg;
}
// explicitly show diff
if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) {
escape = false;
if (!(utils.isString(actual) && utils.isString(expected))) {
err.actual = actual = utils.stringify(actual);
err.expected = expected = utils.stringify(expected);
}
fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
var match = message.match(/^([^:]+): expected/);
msg = '\n ' + color('error message', match ? match[1] : msg);
if (exports.inlineDiffs) {
msg += inlineDiff(err, escape);
} else {
msg += unifiedDiff(err, escape);
}
}
// indent stack trace
stack = stack.replace(/^/gm, ' ');
console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
});
};
/**
* Initialize a new `Base` reporter.
*
* All other reporters generally
* inherit from this reporter, providing
* stats such as test duration, number
* of tests passed / failed etc.
*
* @param {Runner} runner
* @api public
*/
function Base(runner) {
var stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 };
var failures = this.failures = [];
if (!runner) {
return;
}
this.runner = runner;
runner.stats = stats;
runner.on('start', function() {
stats.start = new Date();
});
runner.on('suite', function(suite) {
stats.suites = stats.suites || 0;
suite.root || stats.suites++;
});
runner.on('test end', function() {
stats.tests = stats.tests || 0;
stats.tests++;
});
runner.on('pass', function(test) {
stats.passes = stats.passes || 0;
if (test.duration > test.slow()) {
test.speed = 'slow';
} else if (test.duration > test.slow() / 2) {
test.speed = 'medium';
} else {
test.speed = 'fast';
}
stats.passes++;
});
runner.on('fail', function(test, err) {
stats.failures = stats.failures || 0;
stats.failures++;
test.err = err;
failures.push(test);
});
runner.on('end', function() {
stats.end = new Date();
stats.duration = new Date() - stats.start;
});
runner.on('pending', function() {
stats.pending++;
});
}
/**
* Output common epilogue used by many of
* the bundled reporters.
*
* @api public
*/
Base.prototype.epilogue = function() {
var stats = this.stats;
var fmt;
console.log();
// passes
fmt = color('bright pass', ' ')
+ color('green', ' %d passing')
+ color('light', ' (%s)');
console.log(fmt,
stats.passes || 0,
ms(stats.duration));
// pending
if (stats.pending) {
fmt = color('pending', ' ')
+ color('pending', ' %d pending');
console.log(fmt, stats.pending);
}
// failures
if (stats.failures) {
fmt = color('fail', ' %d failing');
console.log(fmt, stats.failures);
Base.list(this.failures);
console.log();
}
console.log();
};
/**
* Pad the given `str` to `len`.
*
* @api private
* @param {string} str
* @param {string} len
* @return {string}
*/
function pad(str, len) {
str = String(str);
return Array(len - str.length + 1).join(' ') + str;
}
/**
* Returns an inline diff between 2 strings with coloured ANSI output
*
* @api private
* @param {Error} err with actual/expected
* @param {boolean} escape
* @return {string} Diff
*/
function inlineDiff(err, escape) {
var msg = errorDiff(err, 'WordsWithSpace', escape);
// linenos
var lines = msg.split('\n');
if (lines.length > 4) {
var width = String(lines.length).length;
msg = lines.map(function(str, i) {
return pad(++i, width) + ' |' + ' ' + str;
}).join('\n');
}
// legend
msg = '\n'
+ color('diff removed', 'actual')
+ ' '
+ color('diff added', 'expected')
+ '\n\n'
+ msg
+ '\n';
// indent
msg = msg.replace(/^/gm, ' ');
return msg;
}
/**
* Returns a unified diff between two strings.
*
* @api private
* @param {Error} err with actual/expected
* @param {boolean} escape
* @return {string} The diff.
*/
function unifiedDiff(err, escape) {
var indent = ' ';
function cleanUp(line) {
if (escape) {
line = escapeInvisibles(line);
}
if (line[0] === '+') {
return indent + colorLines('diff added', line);
}
if (line[0] === '-') {
return indent + colorLines('diff removed', line);
}
if (line.match(/\@\@/)) {
return null;
}
if (line.match(/\\ No newline/)) {
return null;
}
return indent + line;
}
function notBlank(line) {
return typeof line !== 'undefined' && line !== null;
}
var msg = diff.createPatch('string', err.actual, err.expected);
var lines = msg.split('\n').splice(4);
return '\n '
+ colorLines('diff added', '+ expected') + ' '
+ colorLines('diff removed', '- actual')
+ '\n\n'
+ lines.map(cleanUp).filter(notBlank).join('\n');
}
/**
* Return a character diff for `err`.
*
* @api private
* @param {Error} err
* @param {string} type
* @param {boolean} escape
* @return {string}
*/
function errorDiff(err, type, escape) {
var actual = escape ? escapeInvisibles(err.actual) : err.actual;
var expected = escape ? escapeInvisibles(err.expected) : err.expected;
return diff['diff' + type](actual, expected).map(function(str) {
if (str.added) {
return colorLines('diff added', str.value);
}
if (str.removed) {
return colorLines('diff removed', str.value);
}
return str.value;
}).join('');
}
/**
* Returns a string with all invisible characters in plain text
*
* @api private
* @param {string} line
* @return {string}
*/
function escapeInvisibles(line) {
return line.replace(/\t/g, '<tab>')
.replace(/\r/g, '<CR>')
.replace(/\n/g, '<LF>\n');
}
/**
* Color lines for `str`, using the color `name`.
*
* @api private
* @param {string} name
* @param {string} str
* @return {string}
*/
function colorLines(name, str) {
return str.split('\n').map(function(str) {
return color(name, str);
}).join('\n');
}
/**
* Object#toString reference.
*/
var objToString = Object.prototype.toString;
/**
* Check that a / b have the same type.
*
* @api private
* @param {Object} a
* @param {Object} b
* @return {boolean}
*/
function sameType(a, b) {
return objToString.call(a) === objToString.call(b);
}

View File

@@ -0,0 +1,62 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var utils = require('../utils');
/**
* Expose `Doc`.
*/
exports = module.exports = Doc;
/**
* Initialize a new `Doc` reporter.
*
* @param {Runner} runner
* @api public
*/
function Doc(runner) {
Base.call(this, runner);
var indents = 2;
function indent() {
return Array(indents).join(' ');
}
runner.on('suite', function(suite) {
if (suite.root) {
return;
}
++indents;
console.log('%s<section class="suite">', indent());
++indents;
console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title));
console.log('%s<dl>', indent());
});
runner.on('suite end', function(suite) {
if (suite.root) {
return;
}
console.log('%s</dl>', indent());
--indents;
console.log('%s</section>', indent());
--indents;
});
runner.on('pass', function(test) {
console.log('%s <dt>%s</dt>', indent(), utils.escape(test.title));
var code = utils.escape(utils.clean(test.fn.toString()));
console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code);
});
runner.on('fail', function(test, err) {
console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title));
var code = utils.escape(utils.clean(test.fn.toString()));
console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code);
console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err));
});
}

View File

@@ -0,0 +1,66 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
/**
* Expose `Dot`.
*/
exports = module.exports = Dot;
/**
* Initialize a new `Dot` matrix test reporter.
*
* @api public
* @param {Runner} runner
*/
function Dot(runner) {
Base.call(this, runner);
var self = this;
var width = Base.window.width * .75 | 0;
var n = -1;
runner.on('start', function() {
process.stdout.write('\n');
});
runner.on('pending', function() {
if (++n % width === 0) {
process.stdout.write('\n ');
}
process.stdout.write(color('pending', Base.symbols.dot));
});
runner.on('pass', function(test) {
if (++n % width === 0) {
process.stdout.write('\n ');
}
if (test.speed === 'slow') {
process.stdout.write(color('bright yellow', Base.symbols.dot));
} else {
process.stdout.write(color(test.speed, Base.symbols.dot));
}
});
runner.on('fail', function() {
if (++n % width === 0) {
process.stdout.write('\n ');
}
process.stdout.write(color('fail', Base.symbols.dot));
});
runner.on('end', function() {
console.log();
self.epilogue();
});
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Dot, Base);

View File

@@ -0,0 +1,56 @@
/**
* Module dependencies.
*/
var JSONCov = require('./json-cov');
var readFileSync = require('fs').readFileSync;
var join = require('path').join;
/**
* Expose `HTMLCov`.
*/
exports = module.exports = HTMLCov;
/**
* Initialize a new `JsCoverage` reporter.
*
* @api public
* @param {Runner} runner
*/
function HTMLCov(runner) {
var jade = require('jade');
var file = join(__dirname, '/templates/coverage.jade');
var str = readFileSync(file, 'utf8');
var fn = jade.compile(str, { filename: file });
var self = this;
JSONCov.call(this, runner, false);
runner.on('end', function() {
process.stdout.write(fn({
cov: self.cov,
coverageClass: coverageClass
}));
});
}
/**
* Return coverage class for a given coverage percentage.
*
* @api private
* @param {number} coveragePctg
* @return {string}
*/
function coverageClass(coveragePctg) {
if (coveragePctg >= 75) {
return 'high';
}
if (coveragePctg >= 50) {
return 'medium';
}
if (coveragePctg >= 25) {
return 'low';
}
return 'terrible';
}

View File

@@ -0,0 +1,326 @@
/* eslint-env browser */
/**
* Module dependencies.
*/
var Base = require('./base');
var utils = require('../utils');
var Progress = require('../browser/progress');
var escapeRe = require('escape-string-regexp');
var escape = utils.escape;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */
/**
* Expose `HTML`.
*/
exports = module.exports = HTML;
/**
* Stats template.
*/
var statsTemplate = '<ul id="mocha-stats">'
+ '<li class="progress"><canvas width="40" height="40"></canvas></li>'
+ '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>'
+ '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>'
+ '<li class="duration">duration: <em>0</em>s</li>'
+ '</ul>';
/**
* Initialize a new `HTML` reporter.
*
* @api public
* @param {Runner} runner
*/
function HTML(runner) {
Base.call(this, runner);
var self = this;
var stats = this.stats;
var stat = fragment(statsTemplate);
var items = stat.getElementsByTagName('li');
var passes = items[1].getElementsByTagName('em')[0];
var passesLink = items[1].getElementsByTagName('a')[0];
var failures = items[2].getElementsByTagName('em')[0];
var failuresLink = items[2].getElementsByTagName('a')[0];
var duration = items[3].getElementsByTagName('em')[0];
var canvas = stat.getElementsByTagName('canvas')[0];
var report = fragment('<ul id="mocha-report"></ul>');
var stack = [report];
var progress;
var ctx;
var root = document.getElementById('mocha');
if (canvas.getContext) {
var ratio = window.devicePixelRatio || 1;
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width *= ratio;
canvas.height *= ratio;
ctx = canvas.getContext('2d');
ctx.scale(ratio, ratio);
progress = new Progress();
}
if (!root) {
return error('#mocha div missing, add it to your document');
}
// pass toggle
on(passesLink, 'click', function() {
unhide();
var name = (/pass/).test(report.className) ? '' : ' pass';
report.className = report.className.replace(/fail|pass/g, '') + name;
if (report.className.trim()) {
hideSuitesWithout('test pass');
}
});
// failure toggle
on(failuresLink, 'click', function() {
unhide();
var name = (/fail/).test(report.className) ? '' : ' fail';
report.className = report.className.replace(/fail|pass/g, '') + name;
if (report.className.trim()) {
hideSuitesWithout('test fail');
}
});
root.appendChild(stat);
root.appendChild(report);
if (progress) {
progress.size(40);
}
runner.on('suite', function(suite) {
if (suite.root) {
return;
}
// suite
var url = self.suiteURL(suite);
var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));
// container
stack[0].appendChild(el);
stack.unshift(document.createElement('ul'));
el.appendChild(stack[0]);
});
runner.on('suite end', function(suite) {
if (suite.root) {
return;
}
stack.shift();
});
runner.on('fail', function(test) {
if (test.type === 'hook') {
runner.emit('test end', test);
}
});
runner.on('test end', function(test) {
// TODO: add to stats
var percent = stats.tests / this.total * 100 | 0;
if (progress) {
progress.update(percent).draw(ctx);
}
// update stats
var ms = new Date() - stats.start;
text(passes, stats.passes);
text(failures, stats.failures);
text(duration, (ms / 1000).toFixed(2));
// test
var el;
if (test.state === 'passed') {
var url = self.testURL(test);
el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
} else if (test.pending) {
el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
} else {
el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
var stackString; // Note: Includes leading newline
var message = test.err.toString();
// <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
// check for the result of the stringifying.
if (message === '[object Error]') {
message = test.err.message;
}
if (test.err.stack) {
var indexOfMessage = test.err.stack.indexOf(test.err.message);
if (indexOfMessage === -1) {
stackString = test.err.stack;
} else {
stackString = test.err.stack.substr(test.err.message.length + indexOfMessage);
}
} else if (test.err.sourceURL && test.err.line !== undefined) {
// Safari doesn't give you a stack. Let's at least provide a source line.
stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
}
stackString = stackString || '';
if (test.err.htmlMessage && stackString) {
el.appendChild(fragment('<div class="html-error">%s\n<pre class="error">%e</pre></div>', test.err.htmlMessage, stackString));
} else if (test.err.htmlMessage) {
el.appendChild(fragment('<div class="html-error">%s</div>', test.err.htmlMessage));
} else {
el.appendChild(fragment('<pre class="error">%e%e</pre>', message, stackString));
}
}
// toggle code
// TODO: defer
if (!test.pending) {
var h2 = el.getElementsByTagName('h2')[0];
on(h2, 'click', function() {
pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
});
var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));
el.appendChild(pre);
pre.style.display = 'none';
}
// Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
if (stack[0]) {
stack[0].appendChild(el);
}
});
}
/**
* Makes a URL, preserving querystring ("search") parameters.
*
* @param {string} s
* @return {string} A new URL.
*/
function makeUrl(s) {
var search = window.location.search;
// Remove previous grep query parameter if present
if (search) {
search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
}
return window.location.pathname + (search ? search + '&' : '?') + 'grep=' + encodeURIComponent(escapeRe(s));
}
/**
* Provide suite URL.
*
* @param {Object} [suite]
*/
HTML.prototype.suiteURL = function(suite) {
return makeUrl(suite.fullTitle());
};
/**
* Provide test URL.
*
* @param {Object} [test]
*/
HTML.prototype.testURL = function(test) {
return makeUrl(test.fullTitle());
};
/**
* Display error `msg`.
*
* @param {string} msg
*/
function error(msg) {
document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
}
/**
* Return a DOM fragment from `html`.
*
* @param {string} html
*/
function fragment(html) {
var args = arguments;
var div = document.createElement('div');
var i = 1;
div.innerHTML = html.replace(/%([se])/g, function(_, type) {
switch (type) {
case 's': return String(args[i++]);
case 'e': return escape(args[i++]);
// no default
}
});
return div.firstChild;
}
/**
* Check for suites that do not have elements
* with `classname`, and hide them.
*
* @param {text} classname
*/
function hideSuitesWithout(classname) {
var suites = document.getElementsByClassName('suite');
for (var i = 0; i < suites.length; i++) {
var els = suites[i].getElementsByClassName(classname);
if (!els.length) {
suites[i].className += ' hidden';
}
}
}
/**
* Unhide .hidden suites.
*/
function unhide() {
var els = document.getElementsByClassName('suite hidden');
for (var i = 0; i < els.length; ++i) {
els[i].className = els[i].className.replace('suite hidden', 'suite');
}
}
/**
* Set an element's text contents.
*
* @param {HTMLElement} el
* @param {string} contents
*/
function text(el, contents) {
if (el.textContent) {
el.textContent = contents;
} else {
el.innerText = contents;
}
}
/**
* Listen on `event` with callback `fn`.
*/
function on(el, event, fn) {
if (el.addEventListener) {
el.addEventListener(event, fn, false);
} else {
el.attachEvent('on' + event, fn);
}
}

View File

@@ -0,0 +1,19 @@
// Alias exports to a their normalized format Mocha#reporter to prevent a need
// for dynamic (try/catch) requires, which Browserify doesn't handle.
exports.Base = exports.base = require('./base');
exports.Dot = exports.dot = require('./dot');
exports.Doc = exports.doc = require('./doc');
exports.TAP = exports.tap = require('./tap');
exports.JSON = exports.json = require('./json');
exports.HTML = exports.html = require('./html');
exports.List = exports.list = require('./list');
exports.Min = exports.min = require('./min');
exports.Spec = exports.spec = require('./spec');
exports.Nyan = exports.nyan = require('./nyan');
exports.XUnit = exports.xunit = require('./xunit');
exports.Markdown = exports.markdown = require('./markdown');
exports.Progress = exports.progress = require('./progress');
exports.Landing = exports.landing = require('./landing');
exports.JSONCov = exports['json-cov'] = require('./json-cov');
exports.HTMLCov = exports['html-cov'] = require('./html-cov');
exports.JSONStream = exports['json-stream'] = require('./json-stream');

View File

@@ -0,0 +1,150 @@
/**
* Module dependencies.
*/
var Base = require('./base');
/**
* Expose `JSONCov`.
*/
exports = module.exports = JSONCov;
/**
* Initialize a new `JsCoverage` reporter.
*
* @api public
* @param {Runner} runner
* @param {boolean} output
*/
function JSONCov(runner, output) {
Base.call(this, runner);
output = arguments.length === 1 || output;
var self = this;
var tests = [];
var failures = [];
var passes = [];
runner.on('test end', function(test) {
tests.push(test);
});
runner.on('pass', function(test) {
passes.push(test);
});
runner.on('fail', function(test) {
failures.push(test);
});
runner.on('end', function() {
var cov = global._$jscoverage || {};
var result = self.cov = map(cov);
result.stats = self.stats;
result.tests = tests.map(clean);
result.failures = failures.map(clean);
result.passes = passes.map(clean);
if (!output) {
return;
}
process.stdout.write(JSON.stringify(result, null, 2));
});
}
/**
* Map jscoverage data to a JSON structure
* suitable for reporting.
*
* @api private
* @param {Object} cov
* @return {Object}
*/
function map(cov) {
var ret = {
instrumentation: 'node-jscoverage',
sloc: 0,
hits: 0,
misses: 0,
coverage: 0,
files: []
};
for (var filename in cov) {
if (Object.prototype.hasOwnProperty.call(cov, filename)) {
var data = coverage(filename, cov[filename]);
ret.files.push(data);
ret.hits += data.hits;
ret.misses += data.misses;
ret.sloc += data.sloc;
}
}
ret.files.sort(function(a, b) {
return a.filename.localeCompare(b.filename);
});
if (ret.sloc > 0) {
ret.coverage = (ret.hits / ret.sloc) * 100;
}
return ret;
}
/**
* Map jscoverage data for a single source file
* to a JSON structure suitable for reporting.
*
* @api private
* @param {string} filename name of the source file
* @param {Object} data jscoverage coverage data
* @return {Object}
*/
function coverage(filename, data) {
var ret = {
filename: filename,
coverage: 0,
hits: 0,
misses: 0,
sloc: 0,
source: {}
};
data.source.forEach(function(line, num) {
num++;
if (data[num] === 0) {
ret.misses++;
ret.sloc++;
} else if (data[num] !== undefined) {
ret.hits++;
ret.sloc++;
}
ret.source[num] = {
source: line,
coverage: data[num] === undefined ? '' : data[num]
};
});
ret.coverage = ret.hits / ret.sloc * 100;
return ret;
}
/**
* Return a plain-object representation of `test`
* free of cyclic properties etc.
*
* @api private
* @param {Object} test
* @return {Object}
*/
function clean(test) {
return {
duration: test.duration,
fullTitle: test.fullTitle(),
title: test.title
};
}

View File

@@ -0,0 +1,59 @@
/**
* Module dependencies.
*/
var Base = require('./base');
/**
* Expose `List`.
*/
exports = module.exports = List;
/**
* Initialize a new `List` test reporter.
*
* @api public
* @param {Runner} runner
*/
function List(runner) {
Base.call(this, runner);
var self = this;
var total = runner.total;
runner.on('start', function() {
console.log(JSON.stringify(['start', { total: total }]));
});
runner.on('pass', function(test) {
console.log(JSON.stringify(['pass', clean(test)]));
});
runner.on('fail', function(test, err) {
test = clean(test);
test.err = err.message;
test.stack = err.stack || null;
console.log(JSON.stringify(['fail', test]));
});
runner.on('end', function() {
process.stdout.write(JSON.stringify(['end', self.stats]));
});
}
/**
* Return a plain-object representation of `test`
* free of cyclic properties etc.
*
* @api private
* @param {Object} test
* @return {Object}
*/
function clean(test) {
return {
title: test.title,
fullTitle: test.fullTitle(),
duration: test.duration
};
}

View File

@@ -0,0 +1,89 @@
/**
* Module dependencies.
*/
var Base = require('./base');
/**
* Expose `JSON`.
*/
exports = module.exports = JSONReporter;
/**
* Initialize a new `JSON` reporter.
*
* @api public
* @param {Runner} runner
*/
function JSONReporter(runner) {
Base.call(this, runner);
var self = this;
var tests = [];
var pending = [];
var failures = [];
var passes = [];
runner.on('test end', function(test) {
tests.push(test);
});
runner.on('pass', function(test) {
passes.push(test);
});
runner.on('fail', function(test) {
failures.push(test);
});
runner.on('pending', function(test) {
pending.push(test);
});
runner.on('end', function() {
var obj = {
stats: self.stats,
tests: tests.map(clean),
pending: pending.map(clean),
failures: failures.map(clean),
passes: passes.map(clean)
};
runner.testResults = obj;
process.stdout.write(JSON.stringify(obj, null, 2));
});
}
/**
* Return a plain-object representation of `test`
* free of cyclic properties etc.
*
* @api private
* @param {Object} test
* @return {Object}
*/
function clean(test) {
return {
title: test.title,
fullTitle: test.fullTitle(),
duration: test.duration,
err: errorJSON(test.err || {})
};
}
/**
* Transform `error` into a JSON object.
*
* @api private
* @param {Error} err
* @return {Object}
*/
function errorJSON(err) {
var res = {};
Object.getOwnPropertyNames(err).forEach(function(key) {
res[key] = err[key];
}, err);
return res;
}

View File

@@ -0,0 +1,92 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
var cursor = Base.cursor;
var color = Base.color;
/**
* Expose `Landing`.
*/
exports = module.exports = Landing;
/**
* Airplane color.
*/
Base.colors.plane = 0;
/**
* Airplane crash color.
*/
Base.colors['plane crash'] = 31;
/**
* Runway color.
*/
Base.colors.runway = 90;
/**
* Initialize a new `Landing` reporter.
*
* @api public
* @param {Runner} runner
*/
function Landing(runner) {
Base.call(this, runner);
var self = this;
var width = Base.window.width * .75 | 0;
var total = runner.total;
var stream = process.stdout;
var plane = color('plane', '✈');
var crashed = -1;
var n = 0;
function runway() {
var buf = Array(width).join('-');
return ' ' + color('runway', buf);
}
runner.on('start', function() {
stream.write('\n\n\n ');
cursor.hide();
});
runner.on('test end', function(test) {
// check if the plane crashed
var col = crashed === -1 ? width * ++n / total | 0 : crashed;
// show the crash
if (test.state === 'failed') {
plane = color('plane crash', '✈');
crashed = col;
}
// render landing strip
stream.write('\u001b[' + (width + 1) + 'D\u001b[2A');
stream.write(runway());
stream.write('\n ');
stream.write(color('runway', Array(col).join('⋅')));
stream.write(plane);
stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
stream.write(runway());
stream.write('\u001b[0m');
});
runner.on('end', function() {
cursor.show();
console.log();
self.epilogue();
});
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Landing, Base);

View File

@@ -0,0 +1,61 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
var cursor = Base.cursor;
/**
* Expose `List`.
*/
exports = module.exports = List;
/**
* Initialize a new `List` test reporter.
*
* @api public
* @param {Runner} runner
*/
function List(runner) {
Base.call(this, runner);
var self = this;
var n = 0;
runner.on('start', function() {
console.log();
});
runner.on('test', function(test) {
process.stdout.write(color('pass', ' ' + test.fullTitle() + ': '));
});
runner.on('pending', function(test) {
var fmt = color('checkmark', ' -')
+ color('pending', ' %s');
console.log(fmt, test.fullTitle());
});
runner.on('pass', function(test) {
var fmt = color('checkmark', ' ' + Base.symbols.dot)
+ color('pass', ' %s: ')
+ color(test.speed, '%dms');
cursor.CR();
console.log(fmt, test.fullTitle(), test.duration);
});
runner.on('fail', function(test) {
cursor.CR();
console.log(color('fail', ' %d) %s'), ++n, test.fullTitle());
});
runner.on('end', self.epilogue.bind(self));
}
/**
* Inherit from `Base.prototype`.
*/
inherits(List, Base);

View File

@@ -0,0 +1,97 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var utils = require('../utils');
/**
* Constants
*/
var SUITE_PREFIX = '$';
/**
* Expose `Markdown`.
*/
exports = module.exports = Markdown;
/**
* Initialize a new `Markdown` reporter.
*
* @api public
* @param {Runner} runner
*/
function Markdown(runner) {
Base.call(this, runner);
var level = 0;
var buf = '';
function title(str) {
return Array(level).join('#') + ' ' + str;
}
function mapTOC(suite, obj) {
var ret = obj;
var key = SUITE_PREFIX + suite.title;
obj = obj[key] = obj[key] || { suite: suite };
suite.suites.forEach(function(suite) {
mapTOC(suite, obj);
});
return ret;
}
function stringifyTOC(obj, level) {
++level;
var buf = '';
var link;
for (var key in obj) {
if (key === 'suite') {
continue;
}
if (key !== SUITE_PREFIX) {
link = ' - [' + key.substring(1) + ']';
link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
buf += Array(level).join(' ') + link;
}
buf += stringifyTOC(obj[key], level);
}
return buf;
}
function generateTOC(suite) {
var obj = mapTOC(suite, {});
return stringifyTOC(obj, 0);
}
generateTOC(runner.suite);
runner.on('suite', function(suite) {
++level;
var slug = utils.slug(suite.fullTitle());
buf += '<a name="' + slug + '"></a>' + '\n';
buf += title(suite.title) + '\n';
});
runner.on('suite end', function() {
--level;
});
runner.on('pass', function(test) {
var code = utils.clean(test.fn.toString());
buf += test.title + '.\n';
buf += '\n```js\n';
buf += code + '\n';
buf += '```\n\n';
});
runner.on('end', function() {
process.stdout.write('# TOC\n');
process.stdout.write(generateTOC(runner.suite));
process.stdout.write(buf);
});
}

View File

@@ -0,0 +1,36 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
/**
* Expose `Min`.
*/
exports = module.exports = Min;
/**
* Initialize a new `Min` minimal test reporter (best used with --watch).
*
* @api public
* @param {Runner} runner
*/
function Min(runner) {
Base.call(this, runner);
runner.on('start', function() {
// clear screen
process.stdout.write('\u001b[2J');
// set cursor position
process.stdout.write('\u001b[1;3H');
});
runner.on('end', this.epilogue.bind(this));
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Min, Base);

View File

@@ -0,0 +1,261 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
/**
* Expose `Dot`.
*/
exports = module.exports = NyanCat;
/**
* Initialize a new `Dot` matrix test reporter.
*
* @param {Runner} runner
* @api public
*/
function NyanCat(runner) {
Base.call(this, runner);
var self = this;
var width = Base.window.width * .75 | 0;
var nyanCatWidth = this.nyanCatWidth = 11;
this.colorIndex = 0;
this.numberOfLines = 4;
this.rainbowColors = self.generateColors();
this.scoreboardWidth = 5;
this.tick = 0;
this.trajectories = [[], [], [], []];
this.trajectoryWidthMax = (width - nyanCatWidth);
runner.on('start', function() {
Base.cursor.hide();
self.draw();
});
runner.on('pending', function() {
self.draw();
});
runner.on('pass', function() {
self.draw();
});
runner.on('fail', function() {
self.draw();
});
runner.on('end', function() {
Base.cursor.show();
for (var i = 0; i < self.numberOfLines; i++) {
write('\n');
}
self.epilogue();
});
}
/**
* Inherit from `Base.prototype`.
*/
inherits(NyanCat, Base);
/**
* Draw the nyan cat
*
* @api private
*/
NyanCat.prototype.draw = function() {
this.appendRainbow();
this.drawScoreboard();
this.drawRainbow();
this.drawNyanCat();
this.tick = !this.tick;
};
/**
* Draw the "scoreboard" showing the number
* of passes, failures and pending tests.
*
* @api private
*/
NyanCat.prototype.drawScoreboard = function() {
var stats = this.stats;
function draw(type, n) {
write(' ');
write(Base.color(type, n));
write('\n');
}
draw('green', stats.passes);
draw('fail', stats.failures);
draw('pending', stats.pending);
write('\n');
this.cursorUp(this.numberOfLines);
};
/**
* Append the rainbow.
*
* @api private
*/
NyanCat.prototype.appendRainbow = function() {
var segment = this.tick ? '_' : '-';
var rainbowified = this.rainbowify(segment);
for (var index = 0; index < this.numberOfLines; index++) {
var trajectory = this.trajectories[index];
if (trajectory.length >= this.trajectoryWidthMax) {
trajectory.shift();
}
trajectory.push(rainbowified);
}
};
/**
* Draw the rainbow.
*
* @api private
*/
NyanCat.prototype.drawRainbow = function() {
var self = this;
this.trajectories.forEach(function(line) {
write('\u001b[' + self.scoreboardWidth + 'C');
write(line.join(''));
write('\n');
});
this.cursorUp(this.numberOfLines);
};
/**
* Draw the nyan cat
*
* @api private
*/
NyanCat.prototype.drawNyanCat = function() {
var self = this;
var startWidth = this.scoreboardWidth + this.trajectories[0].length;
var dist = '\u001b[' + startWidth + 'C';
var padding = '';
write(dist);
write('_,------,');
write('\n');
write(dist);
padding = self.tick ? ' ' : ' ';
write('_|' + padding + '/\\_/\\ ');
write('\n');
write(dist);
padding = self.tick ? '_' : '__';
var tail = self.tick ? '~' : '^';
write(tail + '|' + padding + this.face() + ' ');
write('\n');
write(dist);
padding = self.tick ? ' ' : ' ';
write(padding + '"" "" ');
write('\n');
this.cursorUp(this.numberOfLines);
};
/**
* Draw nyan cat face.
*
* @api private
* @return {string}
*/
NyanCat.prototype.face = function() {
var stats = this.stats;
if (stats.failures) {
return '( x .x)';
} else if (stats.pending) {
return '( o .o)';
} else if (stats.passes) {
return '( ^ .^)';
}
return '( - .-)';
};
/**
* Move cursor up `n`.
*
* @api private
* @param {number} n
*/
NyanCat.prototype.cursorUp = function(n) {
write('\u001b[' + n + 'A');
};
/**
* Move cursor down `n`.
*
* @api private
* @param {number} n
*/
NyanCat.prototype.cursorDown = function(n) {
write('\u001b[' + n + 'B');
};
/**
* Generate rainbow colors.
*
* @api private
* @return {Array}
*/
NyanCat.prototype.generateColors = function() {
var colors = [];
for (var i = 0; i < (6 * 7); i++) {
var pi3 = Math.floor(Math.PI / 3);
var n = (i * (1.0 / 6));
var r = Math.floor(3 * Math.sin(n) + 3);
var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
colors.push(36 * r + 6 * g + b + 16);
}
return colors;
};
/**
* Apply rainbow to the given `str`.
*
* @api private
* @param {string} str
* @return {string}
*/
NyanCat.prototype.rainbowify = function(str) {
if (!Base.useColors) {
return str;
}
var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
this.colorIndex += 1;
return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
};
/**
* Stdout helper.
*
* @param {string} string A message to write to stdout.
*/
function write(string) {
process.stdout.write(string);
}

View File

@@ -0,0 +1,89 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
var cursor = Base.cursor;
/**
* Expose `Progress`.
*/
exports = module.exports = Progress;
/**
* General progress bar color.
*/
Base.colors.progress = 90;
/**
* Initialize a new `Progress` bar test reporter.
*
* @api public
* @param {Runner} runner
* @param {Object} options
*/
function Progress(runner, options) {
Base.call(this, runner);
var self = this;
var width = Base.window.width * .50 | 0;
var total = runner.total;
var complete = 0;
var lastN = -1;
// default chars
options = options || {};
options.open = options.open || '[';
options.complete = options.complete || '▬';
options.incomplete = options.incomplete || Base.symbols.dot;
options.close = options.close || ']';
options.verbose = false;
// tests started
runner.on('start', function() {
console.log();
cursor.hide();
});
// tests complete
runner.on('test end', function() {
complete++;
var percent = complete / total;
var n = width * percent | 0;
var i = width - n;
if (n === lastN && !options.verbose) {
// Don't re-render the line if it hasn't changed
return;
}
lastN = n;
cursor.CR();
process.stdout.write('\u001b[J');
process.stdout.write(color('progress', ' ' + options.open));
process.stdout.write(Array(n).join(options.complete));
process.stdout.write(Array(i).join(options.incomplete));
process.stdout.write(color('progress', options.close));
if (options.verbose) {
process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
}
});
// tests are complete, output some stats
// and the failures if any
runner.on('end', function() {
cursor.show();
console.log();
self.epilogue();
});
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Progress, Base);

View File

@@ -0,0 +1,83 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
var cursor = Base.cursor;
/**
* Expose `Spec`.
*/
exports = module.exports = Spec;
/**
* Initialize a new `Spec` test reporter.
*
* @api public
* @param {Runner} runner
*/
function Spec(runner) {
Base.call(this, runner);
var self = this;
var indents = 0;
var n = 0;
function indent() {
return Array(indents).join(' ');
}
runner.on('start', function() {
console.log();
});
runner.on('suite', function(suite) {
++indents;
console.log(color('suite', '%s%s'), indent(), suite.title);
});
runner.on('suite end', function() {
--indents;
if (indents === 1) {
console.log();
}
});
runner.on('pending', function(test) {
var fmt = indent() + color('pending', ' - %s');
console.log(fmt, test.title);
});
runner.on('pass', function(test) {
var fmt;
if (test.speed === 'fast') {
fmt = indent()
+ color('checkmark', ' ' + Base.symbols.ok)
+ color('pass', ' %s');
cursor.CR();
console.log(fmt, test.title);
} else {
fmt = indent()
+ color('checkmark', ' ' + Base.symbols.ok)
+ color('pass', ' %s')
+ color(test.speed, ' (%dms)');
cursor.CR();
console.log(fmt, test.title, test.duration);
}
});
runner.on('fail', function(test) {
cursor.CR();
console.log(indent() + color('fail', ' %d) %s'), ++n, test.title);
});
runner.on('end', self.epilogue.bind(self));
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Spec, Base);

View File

@@ -0,0 +1,68 @@
/**
* Module dependencies.
*/
var Base = require('./base');
/**
* Expose `TAP`.
*/
exports = module.exports = TAP;
/**
* Initialize a new `TAP` reporter.
*
* @api public
* @param {Runner} runner
*/
function TAP(runner) {
Base.call(this, runner);
var n = 1;
var passes = 0;
var failures = 0;
runner.on('start', function() {
var total = runner.grepTotal(runner.suite);
console.log('%d..%d', 1, total);
});
runner.on('test end', function() {
++n;
});
runner.on('pending', function(test) {
console.log('ok %d %s # SKIP -', n, title(test));
});
runner.on('pass', function(test) {
passes++;
console.log('ok %d %s', n, title(test));
});
runner.on('fail', function(test, err) {
failures++;
console.log('not ok %d %s', n, title(test));
if (err.stack) {
console.log(err.stack.replace(/^/gm, ' '));
}
});
runner.on('end', function() {
console.log('# tests ' + (passes + failures));
console.log('# pass ' + passes);
console.log('# fail ' + failures);
});
}
/**
* Return a TAP-safe title of `test`
*
* @api private
* @param {Object} test
* @return {String}
*/
function title(test) {
return test.fullTitle().replace(/#/g, '');
}

View File

@@ -0,0 +1,51 @@
doctype html
html
head
title Coverage
meta(charset='utf-8')
include script.html
include style.html
body
#coverage
h1#overview Coverage
include menu
#stats(class=coverageClass(cov.coverage))
.percentage #{cov.coverage | 0}%
.sloc= cov.sloc
.hits= cov.hits
.misses= cov.misses
#files
for file in cov.files
.file
h2(id=file.filename)= file.filename
#stats(class=coverageClass(file.coverage))
.percentage #{file.coverage | 0}%
.sloc= file.sloc
.hits= file.hits
.misses= file.misses
table#source
thead
tr
th Line
th Hits
th Source
tbody
for line, number in file.source
if line.coverage > 0
tr.hit
td.line= number
td.hits= line.coverage
td.source= line.source
else if 0 === line.coverage
tr.miss
td.line= number
td.hits 0
td.source= line.source
else
tr
td.line= number
td.hits
td.source= line.source || ' '

View File

@@ -0,0 +1,13 @@
#menu
li
a(href='#overview') overview
for file in cov.files
li
span.cov(class=coverageClass(file.coverage)) #{file.coverage | 0}
a(href='##{file.filename}')
segments = file.filename.split('/')
basename = segments.pop()
if segments.length
span.dirname= segments.join('/') + '/'
span.basename= basename
a#logo(href='http://mochajs.org/') m

View File

@@ -0,0 +1,34 @@
<script>
headings = [];
onload = function(){
headings = document.querySelectorAll('h2');
};
onscroll = function(e){
var heading = find(window.scrollY);
if (!heading) return;
var links = document.querySelectorAll('#menu a')
, link;
for (var i = 0, len = links.length; i < len; ++i) {
link = links[i];
link.className = link.getAttribute('href') == '#' + heading.id
? 'active'
: '';
}
};
function find(y) {
var i = headings.length
, heading;
while (i--) {
heading = headings[i];
if (y >= heading.offsetTop) {
return heading;
}
}
}
</script>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,169 @@
/**
* Module dependencies.
*/
var Base = require('./base');
var utils = require('../utils');
var inherits = utils.inherits;
var fs = require('fs');
var escape = utils.escape;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */
/**
* Expose `XUnit`.
*/
exports = module.exports = XUnit;
/**
* Initialize a new `XUnit` reporter.
*
* @api public
* @param {Runner} runner
*/
function XUnit(runner, options) {
Base.call(this, runner);
var stats = this.stats;
var tests = [];
var self = this;
if (options.reporterOptions && options.reporterOptions.output) {
if (!fs.createWriteStream) {
throw new Error('file output not supported in browser');
}
self.fileStream = fs.createWriteStream(options.reporterOptions.output);
}
runner.on('pending', function(test) {
tests.push(test);
});
runner.on('pass', function(test) {
tests.push(test);
});
runner.on('fail', function(test) {
tests.push(test);
});
runner.on('end', function() {
self.write(tag('testsuite', {
name: 'Mocha Tests',
tests: stats.tests,
failures: stats.failures,
errors: stats.failures,
skipped: stats.tests - stats.failures - stats.passes,
timestamp: (new Date()).toUTCString(),
time: (stats.duration / 1000) || 0
}, false));
tests.forEach(function(t) {
self.test(t);
});
self.write('</testsuite>');
});
}
/**
* Inherit from `Base.prototype`.
*/
inherits(XUnit, Base);
/**
* Override done to close the stream (if it's a file).
*
* @param failures
* @param {Function} fn
*/
XUnit.prototype.done = function(failures, fn) {
if (this.fileStream) {
this.fileStream.end(function() {
fn(failures);
});
} else {
fn(failures);
}
};
/**
* Write out the given line.
*
* @param {string} line
*/
XUnit.prototype.write = function(line) {
if (this.fileStream) {
this.fileStream.write(line + '\n');
} else {
console.log(line);
}
};
/**
* Output tag for the given `test.`
*
* @param {Test} test
*/
XUnit.prototype.test = function(test) {
var attrs = {
classname: test.parent.fullTitle(),
name: test.title,
time: (test.duration / 1000) || 0
};
if (test.state === 'failed') {
var err = test.err;
this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + '\n' + err.stack))));
} else if (test.pending) {
this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
} else {
this.write(tag('testcase', attrs, true));
}
};
/**
* HTML tag helper.
*
* @param name
* @param attrs
* @param close
* @param content
* @return {string}
*/
function tag(name, attrs, close, content) {
var end = close ? '/>' : '>';
var pairs = [];
var tag;
for (var key in attrs) {
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
pairs.push(key + '="' + escape(attrs[key]) + '"');
}
}
tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
if (content) {
tag += content + '</' + name + end;
}
return tag;
}
/**
* Return cdata escaped CDATA `str`.
*/
function cdata(str) {
return '<![CDATA[' + escape(str) + ']]>';
}

View File

@@ -0,0 +1,320 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
var Pending = require('./pending');
var debug = require('debug')('mocha:runnable');
var milliseconds = require('./ms');
var utils = require('./utils');
var inherits = utils.inherits;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */
/**
* Object#toString().
*/
var toString = Object.prototype.toString;
/**
* Expose `Runnable`.
*/
module.exports = Runnable;
/**
* Initialize a new `Runnable` with the given `title` and callback `fn`.
*
* @param {String} title
* @param {Function} fn
* @api private
* @param {string} title
* @param {Function} fn
*/
function Runnable(title, fn) {
this.title = title;
this.fn = fn;
this.async = fn && fn.length;
this.sync = !this.async;
this._timeout = 2000;
this._slow = 75;
this._enableTimeouts = true;
this.timedOut = false;
this._trace = new Error('done() called multiple times');
}
/**
* Inherit from `EventEmitter.prototype`.
*/
inherits(Runnable, EventEmitter);
/**
* Set & get timeout `ms`.
*
* @api private
* @param {number|string} ms
* @return {Runnable|number} ms or Runnable instance.
*/
Runnable.prototype.timeout = function(ms) {
if (!arguments.length) {
return this._timeout;
}
if (ms === 0) {
this._enableTimeouts = false;
}
if (typeof ms === 'string') {
ms = milliseconds(ms);
}
debug('timeout %d', ms);
this._timeout = ms;
if (this.timer) {
this.resetTimeout();
}
return this;
};
/**
* Set & get slow `ms`.
*
* @api private
* @param {number|string} ms
* @return {Runnable|number} ms or Runnable instance.
*/
Runnable.prototype.slow = function(ms) {
if (!arguments.length) {
return this._slow;
}
if (typeof ms === 'string') {
ms = milliseconds(ms);
}
debug('timeout %d', ms);
this._slow = ms;
return this;
};
/**
* Set and get whether timeout is `enabled`.
*
* @api private
* @param {boolean} enabled
* @return {Runnable|boolean} enabled or Runnable instance.
*/
Runnable.prototype.enableTimeouts = function(enabled) {
if (!arguments.length) {
return this._enableTimeouts;
}
debug('enableTimeouts %s', enabled);
this._enableTimeouts = enabled;
return this;
};
/**
* Halt and mark as pending.
*
* @api private
*/
Runnable.prototype.skip = function() {
throw new Pending();
};
/**
* Return the full title generated by recursively concatenating the parent's
* full title.
*
* @api public
* @return {string}
*/
Runnable.prototype.fullTitle = function() {
return this.parent.fullTitle() + ' ' + this.title;
};
/**
* Clear the timeout.
*
* @api private
*/
Runnable.prototype.clearTimeout = function() {
clearTimeout(this.timer);
};
/**
* Inspect the runnable void of private properties.
*
* @api private
* @return {string}
*/
Runnable.prototype.inspect = function() {
return JSON.stringify(this, function(key, val) {
if (key[0] === '_') {
return;
}
if (key === 'parent') {
return '#<Suite>';
}
if (key === 'ctx') {
return '#<Context>';
}
return val;
}, 2);
};
/**
* Reset the timeout.
*
* @api private
*/
Runnable.prototype.resetTimeout = function() {
var self = this;
var ms = this.timeout() || 1e9;
if (!this._enableTimeouts) {
return;
}
this.clearTimeout();
this.timer = setTimeout(function() {
if (!self._enableTimeouts) {
return;
}
self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
self.timedOut = true;
}, ms);
};
/**
* Whitelist a list of globals for this test run.
*
* @api private
* @param {string[]} globals
*/
Runnable.prototype.globals = function(globals) {
this._allowedGlobals = globals;
};
/**
* Run the test and invoke `fn(err)`.
*
* @param {Function} fn
* @api private
*/
Runnable.prototype.run = function(fn) {
var self = this;
var start = new Date();
var ctx = this.ctx;
var finished;
var emitted;
// Sometimes the ctx exists, but it is not runnable
if (ctx && ctx.runnable) {
ctx.runnable(this);
}
// called multiple times
function multiple(err) {
if (emitted) {
return;
}
emitted = true;
self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
}
// finished
function done(err) {
var ms = self.timeout();
if (self.timedOut) {
return;
}
if (finished) {
return multiple(err || self._trace);
}
self.clearTimeout();
self.duration = new Date() - start;
finished = true;
if (!err && self.duration > ms && self._enableTimeouts) {
err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
}
fn(err);
}
// for .resetTimeout()
this.callback = done;
// explicit async with `done` argument
if (this.async) {
this.resetTimeout();
if (this.allowUncaught) {
return callFnAsync(this.fn);
}
try {
callFnAsync(this.fn);
} catch (err) {
done(utils.getError(err));
}
return;
}
if (this.allowUncaught) {
callFn(this.fn);
done();
return;
}
// sync or promise-returning
try {
if (this.pending) {
done();
} else {
callFn(this.fn);
}
} catch (err) {
done(utils.getError(err));
}
function callFn(fn) {
var result = fn.call(ctx);
if (result && typeof result.then === 'function') {
self.resetTimeout();
result
.then(function() {
done();
},
function(reason) {
done(reason || new Error('Promise rejected with no or falsy reason'));
});
} else {
if (self.asyncOnly) {
return done(new Error('--async-only option in use without declaring `done()` or returning a promise'));
}
done();
}
}
function callFnAsync(fn) {
fn.call(ctx, function(err) {
if (err instanceof Error || toString.call(err) === '[object Error]') {
return done(err);
}
if (err) {
if (Object.prototype.toString.call(err) === '[object Object]') {
return done(new Error('done() invoked with non-Error: '
+ JSON.stringify(err)));
}
return done(new Error('done() invoked with non-Error: ' + err));
}
done();
});
}
};

View File

@@ -0,0 +1,840 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
var Pending = require('./pending');
var utils = require('./utils');
var inherits = utils.inherits;
var debug = require('debug')('mocha:runner');
var Runnable = require('./runnable');
var filter = utils.filter;
var indexOf = utils.indexOf;
var keys = utils.keys;
var stackFilter = utils.stackTraceFilter();
var stringify = utils.stringify;
var type = utils.type;
var undefinedError = utils.undefinedError;
/**
* Non-enumerable globals.
*/
var globals = [
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'XMLHttpRequest',
'Date',
'setImmediate',
'clearImmediate'
];
/**
* Expose `Runner`.
*/
module.exports = Runner;
/**
* Initialize a `Runner` for the given `suite`.
*
* Events:
*
* - `start` execution started
* - `end` execution complete
* - `suite` (suite) test suite execution started
* - `suite end` (suite) all tests (and sub-suites) have finished
* - `test` (test) test execution started
* - `test end` (test) test completed
* - `hook` (hook) hook execution started
* - `hook end` (hook) hook complete
* - `pass` (test) test passed
* - `fail` (test, err) test failed
* - `pending` (test) test pending
*
* @api public
* @param {Suite} suite Root suite
* @param {boolean} [delay] Whether or not to delay execution of root suite
* until ready.
*/
function Runner(suite, delay) {
var self = this;
this._globals = [];
this._abort = false;
this._delay = delay;
this.suite = suite;
this.started = false;
this.total = suite.total();
this.failures = 0;
this.on('test end', function(test) {
self.checkGlobals(test);
});
this.on('hook end', function(hook) {
self.checkGlobals(hook);
});
this._defaultGrep = /.*/;
this.grep(this._defaultGrep);
this.globals(this.globalProps().concat(extraGlobals()));
}
/**
* Wrapper for setImmediate, process.nextTick, or browser polyfill.
*
* @param {Function} fn
* @api private
*/
Runner.immediately = global.setImmediate || process.nextTick;
/**
* Inherit from `EventEmitter.prototype`.
*/
inherits(Runner, EventEmitter);
/**
* Run tests with full titles matching `re`. Updates runner.total
* with number of tests matched.
*
* @param {RegExp} re
* @param {Boolean} invert
* @return {Runner} for chaining
* @api public
* @param {RegExp} re
* @param {boolean} invert
* @return {Runner} Runner instance.
*/
Runner.prototype.grep = function(re, invert) {
debug('grep %s', re);
this._grep = re;
this._invert = invert;
this.total = this.grepTotal(this.suite);
return this;
};
/**
* Returns the number of tests matching the grep search for the
* given suite.
*
* @param {Suite} suite
* @return {Number}
* @api public
* @param {Suite} suite
* @return {number}
*/
Runner.prototype.grepTotal = function(suite) {
var self = this;
var total = 0;
suite.eachTest(function(test) {
var match = self._grep.test(test.fullTitle());
if (self._invert) {
match = !match;
}
if (match) {
total++;
}
});
return total;
};
/**
* Return a list of global properties.
*
* @return {Array}
* @api private
*/
Runner.prototype.globalProps = function() {
var props = keys(global);
// non-enumerables
for (var i = 0; i < globals.length; ++i) {
if (~indexOf(props, globals[i])) {
continue;
}
props.push(globals[i]);
}
return props;
};
/**
* Allow the given `arr` of globals.
*
* @param {Array} arr
* @return {Runner} for chaining
* @api public
* @param {Array} arr
* @return {Runner} Runner instance.
*/
Runner.prototype.globals = function(arr) {
if (!arguments.length) {
return this._globals;
}
debug('globals %j', arr);
this._globals = this._globals.concat(arr);
return this;
};
/**
* Check for global variable leaks.
*
* @api private
*/
Runner.prototype.checkGlobals = function(test) {
if (this.ignoreLeaks) {
return;
}
var ok = this._globals;
var globals = this.globalProps();
var leaks;
if (test) {
ok = ok.concat(test._allowedGlobals || []);
}
if (this.prevGlobalsLength === globals.length) {
return;
}
this.prevGlobalsLength = globals.length;
leaks = filterLeaks(ok, globals);
this._globals = this._globals.concat(leaks);
if (leaks.length > 1) {
this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
} else if (leaks.length) {
this.fail(test, new Error('global leak detected: ' + leaks[0]));
}
};
/**
* Fail the given `test`.
*
* @api private
* @param {Test} test
* @param {Error} err
*/
Runner.prototype.fail = function(test, err) {
++this.failures;
test.state = 'failed';
if (!(err instanceof Error || err && typeof err.message === 'string')) {
err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)');
}
err.stack = (this.fullStackTrace || !err.stack)
? err.stack
: stackFilter(err.stack);
this.emit('fail', test, err);
};
/**
* Fail the given `hook` with `err`.
*
* Hook failures work in the following pattern:
* - If bail, then exit
* - Failed `before` hook skips all tests in a suite and subsuites,
* but jumps to corresponding `after` hook
* - Failed `before each` hook skips remaining tests in a
* suite and jumps to corresponding `after each` hook,
* which is run only once
* - Failed `after` hook does not alter
* execution order
* - Failed `after each` hook skips remaining tests in a
* suite and subsuites, but executes other `after each`
* hooks
*
* @api private
* @param {Hook} hook
* @param {Error} err
*/
Runner.prototype.failHook = function(hook, err) {
if (hook.ctx && hook.ctx.currentTest) {
hook.originalTitle = hook.originalTitle || hook.title;
hook.title = hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"';
}
this.fail(hook, err);
if (this.suite.bail()) {
this.emit('end');
}
};
/**
* Run hook `name` callbacks and then invoke `fn()`.
*
* @api private
* @param {string} name
* @param {Function} fn
*/
Runner.prototype.hook = function(name, fn) {
var suite = this.suite;
var hooks = suite['_' + name];
var self = this;
function next(i) {
var hook = hooks[i];
if (!hook) {
return fn();
}
self.currentRunnable = hook;
hook.ctx.currentTest = self.test;
self.emit('hook', hook);
if (!hook.listeners('error').length) {
hook.on('error', function(err) {
self.failHook(hook, err);
});
}
hook.run(function(err) {
var testError = hook.error();
if (testError) {
self.fail(self.test, testError);
}
if (err) {
if (err instanceof Pending) {
suite.pending = true;
} else {
self.failHook(hook, err);
// stop executing hooks, notify callee of hook err
return fn(err);
}
}
self.emit('hook end', hook);
delete hook.ctx.currentTest;
next(++i);
});
}
Runner.immediately(function() {
next(0);
});
};
/**
* Run hook `name` for the given array of `suites`
* in order, and callback `fn(err, errSuite)`.
*
* @api private
* @param {string} name
* @param {Array} suites
* @param {Function} fn
*/
Runner.prototype.hooks = function(name, suites, fn) {
var self = this;
var orig = this.suite;
function next(suite) {
self.suite = suite;
if (!suite) {
self.suite = orig;
return fn();
}
self.hook(name, function(err) {
if (err) {
var errSuite = self.suite;
self.suite = orig;
return fn(err, errSuite);
}
next(suites.pop());
});
}
next(suites.pop());
};
/**
* Run hooks from the top level down.
*
* @param {String} name
* @param {Function} fn
* @api private
*/
Runner.prototype.hookUp = function(name, fn) {
var suites = [this.suite].concat(this.parents()).reverse();
this.hooks(name, suites, fn);
};
/**
* Run hooks from the bottom up.
*
* @param {String} name
* @param {Function} fn
* @api private
*/
Runner.prototype.hookDown = function(name, fn) {
var suites = [this.suite].concat(this.parents());
this.hooks(name, suites, fn);
};
/**
* Return an array of parent Suites from
* closest to furthest.
*
* @return {Array}
* @api private
*/
Runner.prototype.parents = function() {
var suite = this.suite;
var suites = [];
while (suite.parent) {
suite = suite.parent;
suites.push(suite);
}
return suites;
};
/**
* Run the current test and callback `fn(err)`.
*
* @param {Function} fn
* @api private
*/
Runner.prototype.runTest = function(fn) {
var self = this;
var test = this.test;
if (this.asyncOnly) {
test.asyncOnly = true;
}
if (this.allowUncaught) {
test.allowUncaught = true;
return test.run(fn);
}
try {
test.on('error', function(err) {
self.fail(test, err);
});
test.run(fn);
} catch (err) {
fn(err);
}
};
/**
* Run tests in the given `suite` and invoke the callback `fn()` when complete.
*
* @api private
* @param {Suite} suite
* @param {Function} fn
*/
Runner.prototype.runTests = function(suite, fn) {
var self = this;
var tests = suite.tests.slice();
var test;
function hookErr(_, errSuite, after) {
// before/after Each hook for errSuite failed:
var orig = self.suite;
// for failed 'after each' hook start from errSuite parent,
// otherwise start from errSuite itself
self.suite = after ? errSuite.parent : errSuite;
if (self.suite) {
// call hookUp afterEach
self.hookUp('afterEach', function(err2, errSuite2) {
self.suite = orig;
// some hooks may fail even now
if (err2) {
return hookErr(err2, errSuite2, true);
}
// report error suite
fn(errSuite);
});
} else {
// there is no need calling other 'after each' hooks
self.suite = orig;
fn(errSuite);
}
}
function next(err, errSuite) {
// if we bail after first err
if (self.failures && suite._bail) {
return fn();
}
if (self._abort) {
return fn();
}
if (err) {
return hookErr(err, errSuite, true);
}
// next test
test = tests.shift();
// all done
if (!test) {
return fn();
}
// grep
var match = self._grep.test(test.fullTitle());
if (self._invert) {
match = !match;
}
if (!match) {
// Run immediately only if we have defined a grep. When we
// define a grep — It can cause maximum callstack error if
// the grep is doing a large recursive loop by neglecting
// all tests. The run immediately function also comes with
// a performance cost. So we don't want to run immediately
// if we run the whole test suite, because running the whole
// test suite don't do any immediate recursive loops. Thus,
// allowing a JS runtime to breathe.
if (self._grep !== self._defaultGrep) {
Runner.immediately(next);
} else {
next();
}
return;
}
// pending
if (test.pending) {
self.emit('pending', test);
self.emit('test end', test);
return next();
}
// execute test and hook(s)
self.emit('test', self.test = test);
self.hookDown('beforeEach', function(err, errSuite) {
if (suite.pending) {
self.emit('pending', test);
self.emit('test end', test);
return next();
}
if (err) {
return hookErr(err, errSuite, false);
}
self.currentRunnable = self.test;
self.runTest(function(err) {
test = self.test;
if (err) {
if (err instanceof Pending) {
self.emit('pending', test);
} else {
self.fail(test, err);
}
self.emit('test end', test);
if (err instanceof Pending) {
return next();
}
return self.hookUp('afterEach', next);
}
test.state = 'passed';
self.emit('pass', test);
self.emit('test end', test);
self.hookUp('afterEach', next);
});
});
}
this.next = next;
this.hookErr = hookErr;
next();
};
/**
* Run the given `suite` and invoke the callback `fn()` when complete.
*
* @api private
* @param {Suite} suite
* @param {Function} fn
*/
Runner.prototype.runSuite = function(suite, fn) {
var i = 0;
var self = this;
var total = this.grepTotal(suite);
var afterAllHookCalled = false;
debug('run suite %s', suite.fullTitle());
if (!total || (self.failures && suite._bail)) {
return fn();
}
this.emit('suite', this.suite = suite);
function next(errSuite) {
if (errSuite) {
// current suite failed on a hook from errSuite
if (errSuite === suite) {
// if errSuite is current suite
// continue to the next sibling suite
return done();
}
// errSuite is among the parents of current suite
// stop execution of errSuite and all sub-suites
return done(errSuite);
}
if (self._abort) {
return done();
}
var curr = suite.suites[i++];
if (!curr) {
return done();
}
// Avoid grep neglecting large number of tests causing a
// huge recursive loop and thus a maximum call stack error.
// See comment in `this.runTests()` for more information.
if (self._grep !== self._defaultGrep) {
Runner.immediately(function() {
self.runSuite(curr, next);
});
} else {
self.runSuite(curr, next);
}
}
function done(errSuite) {
self.suite = suite;
self.nextSuite = next;
if (afterAllHookCalled) {
fn(errSuite);
} else {
// mark that the afterAll block has been called once
// and so can be skipped if there is an error in it.
afterAllHookCalled = true;
self.hook('afterAll', function() {
self.emit('suite end', suite);
fn(errSuite);
});
}
}
this.nextSuite = next;
this.hook('beforeAll', function(err) {
if (err) {
return done();
}
self.runTests(suite, next);
});
};
/**
* Handle uncaught exceptions.
*
* @param {Error} err
* @api private
*/
Runner.prototype.uncaught = function(err) {
if (err) {
debug('uncaught exception %s', err !== function() {
return this;
}.call(err) ? err : (err.message || err));
} else {
debug('uncaught undefined exception');
err = undefinedError();
}
err.uncaught = true;
var runnable = this.currentRunnable;
if (!runnable) {
runnable = new Runnable('Uncaught error outside test suite');
runnable.parent = this.suite;
if (this.started) {
this.fail(runnable, err);
} else {
// Can't recover from this failure
this.emit('start');
this.fail(runnable, err);
this.emit('end');
}
return;
}
runnable.clearTimeout();
// Ignore errors if complete
if (runnable.state) {
return;
}
this.fail(runnable, err);
// recover from test
if (runnable.type === 'test') {
this.emit('test end', runnable);
this.hookUp('afterEach', this.next);
return;
}
// recover from hooks
if (runnable.type === 'hook') {
var errSuite = this.suite;
// if hook failure is in afterEach block
if (runnable.fullTitle().indexOf('after each') > -1) {
return this.hookErr(err, errSuite, true);
}
// if hook failure is in beforeEach block
if (runnable.fullTitle().indexOf('before each') > -1) {
return this.hookErr(err, errSuite, false);
}
// if hook failure is in after or before blocks
return this.nextSuite(errSuite);
}
// bail
this.emit('end');
};
/**
* Run the root suite and invoke `fn(failures)`
* on completion.
*
* @param {Function} fn
* @return {Runner} for chaining
* @api public
* @param {Function} fn
* @return {Runner} Runner instance.
*/
Runner.prototype.run = function(fn) {
var self = this;
var rootSuite = this.suite;
fn = fn || function() {};
function uncaught(err) {
self.uncaught(err);
}
function start() {
self.started = true;
self.emit('start');
self.runSuite(rootSuite, function() {
debug('finished running');
self.emit('end');
});
}
debug('start');
// callback
this.on('end', function() {
debug('end');
process.removeListener('uncaughtException', uncaught);
fn(self.failures);
});
// uncaught exception
process.on('uncaughtException', uncaught);
if (this._delay) {
// for reporters, I guess.
// might be nice to debounce some dots while we wait.
this.emit('waiting', rootSuite);
rootSuite.once('run', start);
} else {
start();
}
return this;
};
/**
* Cleanly abort execution.
*
* @api public
* @return {Runner} Runner instance.
*/
Runner.prototype.abort = function() {
debug('aborting');
this._abort = true;
return this;
};
/**
* Filter leaks with the given globals flagged as `ok`.
*
* @api private
* @param {Array} ok
* @param {Array} globals
* @return {Array}
*/
function filterLeaks(ok, globals) {
return filter(globals, function(key) {
// Firefox and Chrome exposes iframes as index inside the window object
if (/^d+/.test(key)) {
return false;
}
// in firefox
// if runner runs in an iframe, this iframe's window.getInterface method not init at first
// it is assigned in some seconds
if (global.navigator && (/^getInterface/).test(key)) {
return false;
}
// an iframe could be approached by window[iframeIndex]
// in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
if (global.navigator && (/^\d+/).test(key)) {
return false;
}
// Opera and IE expose global variables for HTML element IDs (issue #243)
if (/^mocha-/.test(key)) {
return false;
}
var matched = filter(ok, function(ok) {
if (~ok.indexOf('*')) {
return key.indexOf(ok.split('*')[0]) === 0;
}
return key === ok;
});
return !matched.length && (!global.navigator || key !== 'onerror');
});
}
/**
* Array of globals dependent on the environment.
*
* @return {Array}
* @api private
*/
function extraGlobals() {
if (typeof process === 'object' && typeof process.version === 'string') {
var parts = process.version.split('.');
var nodeVersion = utils.reduce(parts, function(a, v) {
return a << 8 | v;
});
// 'errno' was renamed to process._errno in v0.9.11.
if (nodeVersion < 0x00090B) {
return ['errno'];
}
}
return [];
}

View File

@@ -0,0 +1,365 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
var Hook = require('./hook');
var utils = require('./utils');
var inherits = utils.inherits;
var debug = require('debug')('mocha:suite');
var milliseconds = require('./ms');
/**
* Expose `Suite`.
*/
exports = module.exports = Suite;
/**
* Create a new `Suite` with the given `title` and parent `Suite`. When a suite
* with the same title is already present, that suite is returned to provide
* nicer reporter and more flexible meta-testing.
*
* @api public
* @param {Suite} parent
* @param {string} title
* @return {Suite}
*/
exports.create = function(parent, title) {
var suite = new Suite(title, parent.ctx);
suite.parent = parent;
if (parent.pending) {
suite.pending = true;
}
title = suite.fullTitle();
parent.addSuite(suite);
return suite;
};
/**
* Initialize a new `Suite` with the given `title` and `ctx`.
*
* @api private
* @param {string} title
* @param {Context} parentContext
*/
function Suite(title, parentContext) {
this.title = title;
function Context() {}
Context.prototype = parentContext;
this.ctx = new Context();
this.suites = [];
this.tests = [];
this.pending = false;
this._beforeEach = [];
this._beforeAll = [];
this._afterEach = [];
this._afterAll = [];
this.root = !title;
this._timeout = 2000;
this._enableTimeouts = true;
this._slow = 75;
this._bail = false;
this.delayed = false;
}
/**
* Inherit from `EventEmitter.prototype`.
*/
inherits(Suite, EventEmitter);
/**
* Return a clone of this `Suite`.
*
* @api private
* @return {Suite}
*/
Suite.prototype.clone = function() {
var suite = new Suite(this.title);
debug('clone');
suite.ctx = this.ctx;
suite.timeout(this.timeout());
suite.enableTimeouts(this.enableTimeouts());
suite.slow(this.slow());
suite.bail(this.bail());
return suite;
};
/**
* Set timeout `ms` or short-hand such as "2s".
*
* @api private
* @param {number|string} ms
* @return {Suite|number} for chaining
*/
Suite.prototype.timeout = function(ms) {
if (!arguments.length) {
return this._timeout;
}
if (ms.toString() === '0') {
this._enableTimeouts = false;
}
if (typeof ms === 'string') {
ms = milliseconds(ms);
}
debug('timeout %d', ms);
this._timeout = parseInt(ms, 10);
return this;
};
/**
* Set timeout to `enabled`.
*
* @api private
* @param {boolean} enabled
* @return {Suite|boolean} self or enabled
*/
Suite.prototype.enableTimeouts = function(enabled) {
if (!arguments.length) {
return this._enableTimeouts;
}
debug('enableTimeouts %s', enabled);
this._enableTimeouts = enabled;
return this;
};
/**
* Set slow `ms` or short-hand such as "2s".
*
* @api private
* @param {number|string} ms
* @return {Suite|number} for chaining
*/
Suite.prototype.slow = function(ms) {
if (!arguments.length) {
return this._slow;
}
if (typeof ms === 'string') {
ms = milliseconds(ms);
}
debug('slow %d', ms);
this._slow = ms;
return this;
};
/**
* Sets whether to bail after first error.
*
* @api private
* @param {boolean} bail
* @return {Suite|number} for chaining
*/
Suite.prototype.bail = function(bail) {
if (!arguments.length) {
return this._bail;
}
debug('bail %s', bail);
this._bail = bail;
return this;
};
/**
* Run `fn(test[, done])` before running tests.
*
* @api private
* @param {string} title
* @param {Function} fn
* @return {Suite} for chaining
*/
Suite.prototype.beforeAll = function(title, fn) {
if (this.pending) {
return this;
}
if (typeof title === 'function') {
fn = title;
title = fn.name;
}
title = '"before all" hook' + (title ? ': ' + title : '');
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
this._beforeAll.push(hook);
this.emit('beforeAll', hook);
return this;
};
/**
* Run `fn(test[, done])` after running tests.
*
* @api private
* @param {string} title
* @param {Function} fn
* @return {Suite} for chaining
*/
Suite.prototype.afterAll = function(title, fn) {
if (this.pending) {
return this;
}
if (typeof title === 'function') {
fn = title;
title = fn.name;
}
title = '"after all" hook' + (title ? ': ' + title : '');
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
this._afterAll.push(hook);
this.emit('afterAll', hook);
return this;
};
/**
* Run `fn(test[, done])` before each test case.
*
* @api private
* @param {string} title
* @param {Function} fn
* @return {Suite} for chaining
*/
Suite.prototype.beforeEach = function(title, fn) {
if (this.pending) {
return this;
}
if (typeof title === 'function') {
fn = title;
title = fn.name;
}
title = '"before each" hook' + (title ? ': ' + title : '');
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
this._beforeEach.push(hook);
this.emit('beforeEach', hook);
return this;
};
/**
* Run `fn(test[, done])` after each test case.
*
* @api private
* @param {string} title
* @param {Function} fn
* @return {Suite} for chaining
*/
Suite.prototype.afterEach = function(title, fn) {
if (this.pending) {
return this;
}
if (typeof title === 'function') {
fn = title;
title = fn.name;
}
title = '"after each" hook' + (title ? ': ' + title : '');
var hook = new Hook(title, fn);
hook.parent = this;
hook.timeout(this.timeout());
hook.enableTimeouts(this.enableTimeouts());
hook.slow(this.slow());
hook.ctx = this.ctx;
this._afterEach.push(hook);
this.emit('afterEach', hook);
return this;
};
/**
* Add a test `suite`.
*
* @api private
* @param {Suite} suite
* @return {Suite} for chaining
*/
Suite.prototype.addSuite = function(suite) {
suite.parent = this;
suite.timeout(this.timeout());
suite.enableTimeouts(this.enableTimeouts());
suite.slow(this.slow());
suite.bail(this.bail());
this.suites.push(suite);
this.emit('suite', suite);
return this;
};
/**
* Add a `test` to this suite.
*
* @api private
* @param {Test} test
* @return {Suite} for chaining
*/
Suite.prototype.addTest = function(test) {
test.parent = this;
test.timeout(this.timeout());
test.enableTimeouts(this.enableTimeouts());
test.slow(this.slow());
test.ctx = this.ctx;
this.tests.push(test);
this.emit('test', test);
return this;
};
/**
* Return the full title generated by recursively concatenating the parent's
* full title.
*
* @api public
* @return {string}
*/
Suite.prototype.fullTitle = function() {
if (this.parent) {
var full = this.parent.fullTitle();
if (full) {
return full + ' ' + this.title;
}
}
return this.title;
};
/**
* Return the total number of tests.
*
* @api public
* @return {number}
*/
Suite.prototype.total = function() {
return utils.reduce(this.suites, function(sum, suite) {
return sum + suite.total();
}, 0) + this.tests.length;
};
/**
* Iterates through each suite recursively to find all tests. Applies a
* function in the format `fn(test)`.
*
* @api private
* @param {Function} fn
* @return {Suite}
*/
Suite.prototype.eachTest = function(fn) {
utils.forEach(this.tests, fn);
utils.forEach(this.suites, function(suite) {
suite.eachTest(fn);
});
return this;
};
/**
* This will run the root suite if we happen to be running in delayed mode.
*/
Suite.prototype.run = function run() {
if (this.root) {
this.emit('run');
}
};

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="mocha.js"></script>
<script>mocha.setup('bdd');</script>
<script src="tests.js"></script>
<script>
mocha.run();
</script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
/**
* Module dependencies.
*/
var Runnable = require('./runnable');
var inherits = require('./utils').inherits;
/**
* Expose `Test`.
*/
module.exports = Test;
/**
* Initialize a new `Test` with the given `title` and callback `fn`.
*
* @api private
* @param {String} title
* @param {Function} fn
*/
function Test(title, fn) {
Runnable.call(this, title, fn);
this.pending = !fn;
this.type = 'test';
}
/**
* Inherit from `Runnable.prototype`.
*/
inherits(Test, Runnable);

View File

@@ -0,0 +1,738 @@
/* 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'];
exports.inherits = require('util').inherits;
/**
* 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 and internal node functions 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' + slash))
|| (~line.indexOf('components' + slash + 'mochajs' + slash))
|| (~line.indexOf('components' + slash + 'mocha' + slash))
|| (~line.indexOf(slash + 'mocha.js'));
}
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 (isMochaInternal(line)) {
return list;
}
if (is.node && isNodeInternal(line)) {
return list;
}
// Clean up cwd(absolute)
list.push(line.replace(cwd, ''));
return list;
}, []);
return stack.join('\n');
};
};

View File

@@ -0,0 +1,305 @@
@charset "utf-8";
body {
margin:0;
}
#mocha {
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 60px 50px;
}
#mocha ul,
#mocha li {
margin: 0;
padding: 0;
}
#mocha ul {
list-style: none;
}
#mocha h1,
#mocha h2 {
margin: 0;
}
#mocha h1 {
margin-top: 15px;
font-size: 1em;
font-weight: 200;
}
#mocha h1 a {
text-decoration: none;
color: inherit;
}
#mocha h1 a:hover {
text-decoration: underline;
}
#mocha .suite .suite h1 {
margin-top: 0;
font-size: .8em;
}
#mocha .hidden {
display: none;
}
#mocha h2 {
font-size: 12px;
font-weight: normal;
cursor: pointer;
}
#mocha .suite {
margin-left: 15px;
}
#mocha .test {
margin-left: 15px;
overflow: hidden;
}
#mocha .test.pending:hover h2::after {
content: '(pending)';
font-family: arial, sans-serif;
}
#mocha .test.pass.medium .duration {
background: #c09853;
}
#mocha .test.pass.slow .duration {
background: #b94a48;
}
#mocha .test.pass::before {
content: '✓';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #00d6b2;
}
#mocha .test.pass .duration {
font-size: 9px;
margin-left: 5px;
padding: 2px 5px;
color: #fff;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
}
#mocha .test.pass.fast .duration {
display: none;
}
#mocha .test.pending {
color: #0b97c4;
}
#mocha .test.pending::before {
content: '◦';
color: #0b97c4;
}
#mocha .test.fail {
color: #c00;
}
#mocha .test.fail pre {
color: black;
}
#mocha .test.fail::before {
content: '✖';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #c00;
}
#mocha .test pre.error {
color: #c00;
max-height: 300px;
overflow: auto;
}
#mocha .test .html-error {
overflow: auto;
color: black;
line-height: 1.5;
display: block;
float: left;
clear: left;
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
max-width: 85%; /*(1)*/
max-width: calc(100% - 42px); /*(2)*/
max-height: 300px;
word-wrap: break-word;
border-bottom-color: #ddd;
-webkit-border-radius: 3px;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-border-radius: 3px;
-moz-box-shadow: 0 1px 3px #eee;
border-radius: 3px;
}
#mocha .test .html-error pre.error {
border: none;
-webkit-border-radius: none;
-webkit-box-shadow: none;
-moz-border-radius: none;
-moz-box-shadow: none;
padding: 0;
margin: 0;
margin-top: 18px;
max-height: none;
}
/**
* (1): approximate for browsers not supporting calc
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
* ^^ seriously
*/
#mocha .test pre {
display: block;
float: left;
clear: left;
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
max-width: 85%; /*(1)*/
max-width: calc(100% - 42px); /*(2)*/
word-wrap: break-word;
border-bottom-color: #ddd;
-webkit-border-radius: 3px;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-border-radius: 3px;
-moz-box-shadow: 0 1px 3px #eee;
border-radius: 3px;
}
#mocha .test h2 {
position: relative;
}
#mocha .test a.replay {
position: absolute;
top: 3px;
right: 0;
text-decoration: none;
vertical-align: middle;
display: block;
width: 15px;
height: 15px;
line-height: 15px;
text-align: center;
background: #eee;
font-size: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
-webkit-transition: opacity 200ms;
-moz-transition: opacity 200ms;
transition: opacity 200ms;
opacity: 0.3;
color: #888;
}
#mocha .test:hover a.replay {
opacity: 1;
}
#mocha-report.pass .test.fail {
display: none;
}
#mocha-report.fail .test.pass {
display: none;
}
#mocha-report.pending .test.pass,
#mocha-report.pending .test.fail {
display: none;
}
#mocha-report.pending .test.pass.pending {
display: block;
}
#mocha-error {
color: #c00;
font-size: 1.5em;
font-weight: 100;
letter-spacing: 1px;
}
#mocha-stats {
position: fixed;
top: 15px;
right: 10px;
font-size: 12px;
margin: 0;
color: #888;
z-index: 1;
}
#mocha-stats .progress {
float: right;
padding-top: 0;
}
#mocha-stats em {
color: black;
}
#mocha-stats a {
text-decoration: none;
color: inherit;
}
#mocha-stats a:hover {
border-bottom: 1px solid #eee;
}
#mocha-stats li {
display: inline-block;
margin: 0 5px;
list-style: none;
padding-top: 11px;
}
#mocha-stats canvas {
width: 40px;
height: 40px;
}
#mocha code .comment { color: #ddd; }
#mocha code .init { color: #2f6fad; }
#mocha code .string { color: #5890ad; }
#mocha code .keyword { color: #8a6343; }
#mocha code .number { color: #2f6fad; }
@media screen and (max-device-width: 480px) {
#mocha {
margin: 60px 0px;
}
#mocha #stats {
position: absolute;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff