base.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /**
  2. * Module dependencies.
  3. */
  4. var tty = require('tty')
  5. , diff = require('diff')
  6. , ms = require('../ms');
  7. /**
  8. * Save timer references to avoid Sinon interfering (see GH-237).
  9. */
  10. var Date = global.Date
  11. , setTimeout = global.setTimeout
  12. , setInterval = global.setInterval
  13. , clearTimeout = global.clearTimeout
  14. , clearInterval = global.clearInterval;
  15. /**
  16. * Check if both stdio streams are associated with a tty.
  17. */
  18. var isatty = tty.isatty(1) && tty.isatty(2);
  19. /**
  20. * Expose `Base`.
  21. */
  22. exports = module.exports = Base;
  23. /**
  24. * Enable coloring by default.
  25. */
  26. exports.useColors = isatty;
  27. /**
  28. * Default color map.
  29. */
  30. exports.colors = {
  31. 'pass': 90
  32. , 'fail': 31
  33. , 'bright pass': 92
  34. , 'bright fail': 91
  35. , 'bright yellow': 93
  36. , 'pending': 36
  37. , 'suite': 0
  38. , 'error title': 0
  39. , 'error message': 31
  40. , 'error stack': 90
  41. , 'checkmark': 32
  42. , 'fast': 90
  43. , 'medium': 33
  44. , 'slow': 31
  45. , 'green': 32
  46. , 'light': 90
  47. , 'diff gutter': 90
  48. , 'diff added': 42
  49. , 'diff removed': 41
  50. };
  51. /**
  52. * Default symbol map.
  53. */
  54. exports.symbols = {
  55. ok: '✓',
  56. err: '✖',
  57. dot: '․'
  58. };
  59. // With node.js on Windows: use symbols available in terminal default fonts
  60. if ('win32' == process.platform) {
  61. exports.symbols.ok = '\u221A';
  62. exports.symbols.err = '\u00D7';
  63. exports.symbols.dot = '.';
  64. }
  65. /**
  66. * Color `str` with the given `type`,
  67. * allowing colors to be disabled,
  68. * as well as user-defined color
  69. * schemes.
  70. *
  71. * @param {String} type
  72. * @param {String} str
  73. * @return {String}
  74. * @api private
  75. */
  76. var color = exports.color = function(type, str) {
  77. if (!exports.useColors) return str;
  78. return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
  79. };
  80. /**
  81. * Expose term window size, with some
  82. * defaults for when stderr is not a tty.
  83. */
  84. exports.window = {
  85. width: isatty
  86. ? process.stdout.getWindowSize
  87. ? process.stdout.getWindowSize(1)[0]
  88. : tty.getWindowSize()[1]
  89. : 75
  90. };
  91. /**
  92. * Expose some basic cursor interactions
  93. * that are common among reporters.
  94. */
  95. exports.cursor = {
  96. hide: function(){
  97. process.stdout.write('\u001b[?25l');
  98. },
  99. show: function(){
  100. process.stdout.write('\u001b[?25h');
  101. },
  102. deleteLine: function(){
  103. process.stdout.write('\u001b[2K');
  104. },
  105. beginningOfLine: function(){
  106. process.stdout.write('\u001b[0G');
  107. },
  108. CR: function(){
  109. exports.cursor.deleteLine();
  110. exports.cursor.beginningOfLine();
  111. }
  112. };
  113. /**
  114. * Outut the given `failures` as a list.
  115. *
  116. * @param {Array} failures
  117. * @api public
  118. */
  119. exports.list = function(failures){
  120. console.error();
  121. failures.forEach(function(test, i){
  122. // format
  123. var fmt = color('error title', ' %s) %s:\n')
  124. + color('error message', ' %s')
  125. + color('error stack', '\n%s\n');
  126. // msg
  127. var err = test.err
  128. , message = err.message || ''
  129. , stack = err.stack || message
  130. , index = stack.indexOf(message) + message.length
  131. , msg = stack.slice(0, index)
  132. , actual = err.actual
  133. , expected = err.expected
  134. , escape = true;
  135. // uncaught
  136. if (err.uncaught) {
  137. msg = 'Uncaught ' + msg;
  138. }
  139. // explicitly show diff
  140. if (err.showDiff && sameType(actual, expected)) {
  141. escape = false;
  142. err.actual = actual = stringify(actual);
  143. err.expected = expected = stringify(expected);
  144. }
  145. // actual / expected diff
  146. if ('string' == typeof actual && 'string' == typeof expected) {
  147. msg = errorDiff(err, 'Words', escape);
  148. // linenos
  149. var lines = msg.split('\n');
  150. if (lines.length > 4) {
  151. var width = String(lines.length).length;
  152. msg = lines.map(function(str, i){
  153. return pad(++i, width) + ' |' + ' ' + str;
  154. }).join('\n');
  155. }
  156. // legend
  157. msg = '\n'
  158. + color('diff removed', 'actual')
  159. + ' '
  160. + color('diff added', 'expected')
  161. + '\n\n'
  162. + msg
  163. + '\n';
  164. // indent
  165. msg = msg.replace(/^/gm, ' ');
  166. fmt = color('error title', ' %s) %s:\n%s')
  167. + color('error stack', '\n%s\n');
  168. }
  169. // indent stack trace without msg
  170. stack = stack.slice(index ? index + 1 : index)
  171. .replace(/^/gm, ' ');
  172. console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
  173. });
  174. };
  175. /**
  176. * Initialize a new `Base` reporter.
  177. *
  178. * All other reporters generally
  179. * inherit from this reporter, providing
  180. * stats such as test duration, number
  181. * of tests passed / failed etc.
  182. *
  183. * @param {Runner} runner
  184. * @api public
  185. */
  186. function Base(runner) {
  187. var self = this
  188. , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
  189. , failures = this.failures = [];
  190. if (!runner) return;
  191. this.runner = runner;
  192. runner.stats = stats;
  193. runner.on('start', function(){
  194. stats.start = new Date;
  195. });
  196. runner.on('suite', function(suite){
  197. stats.suites = stats.suites || 0;
  198. suite.root || stats.suites++;
  199. });
  200. runner.on('test end', function(test){
  201. stats.tests = stats.tests || 0;
  202. stats.tests++;
  203. });
  204. runner.on('pass', function(test){
  205. stats.passes = stats.passes || 0;
  206. var medium = test.slow() / 2;
  207. test.speed = test.duration > test.slow()
  208. ? 'slow'
  209. : test.duration > medium
  210. ? 'medium'
  211. : 'fast';
  212. stats.passes++;
  213. });
  214. runner.on('fail', function(test, err){
  215. stats.failures = stats.failures || 0;
  216. stats.failures++;
  217. test.err = err;
  218. failures.push(test);
  219. });
  220. runner.on('end', function(){
  221. stats.end = new Date;
  222. stats.duration = new Date - stats.start;
  223. });
  224. runner.on('pending', function(){
  225. stats.pending++;
  226. });
  227. }
  228. /**
  229. * Output common epilogue used by many of
  230. * the bundled reporters.
  231. *
  232. * @api public
  233. */
  234. Base.prototype.epilogue = function(){
  235. var stats = this.stats;
  236. var tests;
  237. var fmt;
  238. console.log();
  239. // passes
  240. fmt = color('bright pass', ' ')
  241. + color('green', ' %d passing')
  242. + color('light', ' (%s)');
  243. console.log(fmt,
  244. stats.passes || 0,
  245. ms(stats.duration));
  246. // pending
  247. if (stats.pending) {
  248. fmt = color('pending', ' ')
  249. + color('pending', ' %d pending');
  250. console.log(fmt, stats.pending);
  251. }
  252. // failures
  253. if (stats.failures) {
  254. fmt = color('fail', ' %d failing');
  255. console.error(fmt,
  256. stats.failures);
  257. Base.list(this.failures);
  258. console.error();
  259. }
  260. console.log();
  261. };
  262. /**
  263. * Pad the given `str` to `len`.
  264. *
  265. * @param {String} str
  266. * @param {String} len
  267. * @return {String}
  268. * @api private
  269. */
  270. function pad(str, len) {
  271. str = String(str);
  272. return Array(len - str.length + 1).join(' ') + str;
  273. }
  274. /**
  275. * Return a character diff for `err`.
  276. *
  277. * @param {Error} err
  278. * @return {String}
  279. * @api private
  280. */
  281. function errorDiff(err, type, escape) {
  282. return diff['diff' + type](err.actual, err.expected).map(function(str){
  283. if (escape) {
  284. str.value = str.value
  285. .replace(/\t/g, '<tab>')
  286. .replace(/\r/g, '<CR>')
  287. .replace(/\n/g, '<LF>\n');
  288. }
  289. if (str.added) return colorLines('diff added', str.value);
  290. if (str.removed) return colorLines('diff removed', str.value);
  291. return str.value;
  292. }).join('');
  293. }
  294. /**
  295. * Color lines for `str`, using the color `name`.
  296. *
  297. * @param {String} name
  298. * @param {String} str
  299. * @return {String}
  300. * @api private
  301. */
  302. function colorLines(name, str) {
  303. return str.split('\n').map(function(str){
  304. return color(name, str);
  305. }).join('\n');
  306. }
  307. /**
  308. * Stringify `obj`.
  309. *
  310. * @param {Mixed} obj
  311. * @return {String}
  312. * @api private
  313. */
  314. function stringify(obj) {
  315. if (obj instanceof RegExp) return obj.toString();
  316. return JSON.stringify(obj, null, 2);
  317. }
  318. /**
  319. * Check that a / b have the same type.
  320. *
  321. * @param {Object} a
  322. * @param {Object} b
  323. * @return {Boolean}
  324. * @api private
  325. */
  326. function sameType(a, b) {
  327. a = Object.prototype.toString.call(a);
  328. b = Object.prototype.toString.call(b);
  329. return a == b;
  330. }