json.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. 'use strict';
  2. /**
  3. * @typedef {import('../runner.js')} Runner
  4. */
  5. /**
  6. * @module JSON
  7. */
  8. /**
  9. * Module dependencies.
  10. */
  11. var Base = require('./base');
  12. var fs = require('node:fs');
  13. var path = require('node:path');
  14. const createUnsupportedError = require('../errors').createUnsupportedError;
  15. const utils = require('../utils');
  16. var constants = require('../runner').constants;
  17. var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
  18. var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING;
  19. var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
  20. var EVENT_TEST_END = constants.EVENT_TEST_END;
  21. var EVENT_RUN_END = constants.EVENT_RUN_END;
  22. /**
  23. * Expose `JSON`.
  24. */
  25. exports = module.exports = JSONReporter;
  26. /**
  27. * Constructs a new `JSON` reporter instance.
  28. *
  29. * @public
  30. * @class JSON
  31. * @memberof Mocha.reporters
  32. * @extends Mocha.reporters.Base
  33. * @param {Runner} runner - Instance triggers reporter actions.
  34. * @param {Object} [options] - runner options
  35. */
  36. function JSONReporter(runner, options = {}) {
  37. Base.call(this, runner, options);
  38. var self = this;
  39. var tests = [];
  40. var pending = [];
  41. var failures = [];
  42. var passes = [];
  43. var output;
  44. if (options.reporterOption && options.reporterOption.output) {
  45. if (utils.isBrowser()) {
  46. throw createUnsupportedError('file output not supported in browser');
  47. }
  48. output = options.reporterOption.output;
  49. }
  50. runner.on(EVENT_TEST_END, function (test) {
  51. tests.push(test);
  52. });
  53. runner.on(EVENT_TEST_PASS, function (test) {
  54. passes.push(test);
  55. });
  56. runner.on(EVENT_TEST_FAIL, function (test) {
  57. failures.push(test);
  58. });
  59. runner.on(EVENT_TEST_PENDING, function (test) {
  60. pending.push(test);
  61. });
  62. runner.once(EVENT_RUN_END, function () {
  63. var obj = {
  64. stats: self.stats,
  65. tests: tests.map(clean),
  66. pending: pending.map(clean),
  67. failures: failures.map(clean),
  68. passes: passes.map(clean)
  69. };
  70. runner.testResults = obj;
  71. var json = JSON.stringify(obj, null, 2);
  72. if (output) {
  73. try {
  74. fs.mkdirSync(path.dirname(output), {recursive: true});
  75. fs.writeFileSync(output, json);
  76. } catch (err) {
  77. console.error(
  78. `${Base.symbols.err} [mocha] writing output to "${output}" failed: ${err.message}\n`
  79. );
  80. process.stdout.write(json);
  81. }
  82. } else {
  83. process.stdout.write(json);
  84. }
  85. });
  86. }
  87. /**
  88. * Return a plain-object representation of `test`
  89. * free of cyclic properties etc.
  90. *
  91. * @private
  92. * @param {Object} test
  93. * @return {Object}
  94. */
  95. function clean(test) {
  96. var err = test.err || {};
  97. if (err instanceof Error) {
  98. err = errorJSON(err);
  99. }
  100. return {
  101. title: test.title,
  102. fullTitle: test.fullTitle(),
  103. file: test.file,
  104. duration: test.duration,
  105. currentRetry: test.currentRetry(),
  106. speed: test.speed,
  107. err: cleanCycles(err)
  108. };
  109. }
  110. /**
  111. * Replaces any circular references inside `obj` with '[object Object]'
  112. *
  113. * @private
  114. * @param {Object} obj
  115. * @return {Object}
  116. */
  117. function cleanCycles(obj) {
  118. var cache = [];
  119. return JSON.parse(
  120. JSON.stringify(obj, function (key, value) {
  121. if (typeof value === 'object' && value !== null) {
  122. if (cache.indexOf(value) !== -1) {
  123. // Instead of going in a circle, we'll print [object Object]
  124. return '' + value;
  125. }
  126. cache.push(value);
  127. }
  128. return value;
  129. })
  130. );
  131. }
  132. /**
  133. * Transform an Error object into a JSON object.
  134. *
  135. * @private
  136. * @param {Error} err
  137. * @return {Object}
  138. */
  139. function errorJSON(err) {
  140. var res = {};
  141. Object.getOwnPropertyNames(err).forEach(function (key) {
  142. res[key] = err[key];
  143. }, err);
  144. return res;
  145. }
  146. JSONReporter.description = 'single JSON object';