atom-reporter.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /*
  2. * decaffeinate suggestions:
  3. * DS101: Remove unnecessary use of Array.from
  4. * DS102: Remove unnecessary code created because of implicit returns
  5. * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining
  6. * DS206: Consider reworking classes to avoid initClass
  7. * DS207: Consider shorter variations of null checks
  8. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
  9. */
  10. let AtomReporter;
  11. const path = require('path');
  12. const process = require('process');
  13. const _ = require('underscore-plus');
  14. const grim = require('grim');
  15. const listen = require('../src/delegated-listener');
  16. const ipcHelpers = require('../src/ipc-helpers');
  17. const formatStackTrace = function(spec, message, stackTrace) {
  18. if (message == null) { message = ''; }
  19. if (!stackTrace) { return stackTrace; }
  20. // at ... (.../jasmine.js:1:2)
  21. const jasminePattern = /^\s*at\s+.*\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/;
  22. // at jasmine.Something... (.../jasmine.js:1:2)
  23. const firstJasmineLinePattern = /^\s*at\s+jasmine\.[A-Z][^\s]*\s+\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/;
  24. let lines = [];
  25. for (let line of Array.from(stackTrace.split('\n'))) {
  26. if (firstJasmineLinePattern.test(line)) { break; }
  27. if (!jasminePattern.test(line)) { lines.push(line); }
  28. }
  29. // Remove first line of stack when it is the same as the error message
  30. const errorMatch = lines[0] != null ? lines[0].match(/^Error: (.*)/) : undefined;
  31. if (message.trim() === __guard__(errorMatch != null ? errorMatch[1] : undefined, x => x.trim())) { lines.shift(); }
  32. lines = lines.map(function(line) {
  33. // Only format actual stacktrace lines
  34. if (/^\s*at\s/.test(line)) {
  35. // Needs to occur before path relativization
  36. if ((process.platform === 'win32') && /file:\/\/\//.test(line)) {
  37. // file:///C:/some/file -> C:\some\file
  38. line = line.replace('file:///', '').replace(new RegExp(`${path.posix.sep}`, 'g'), path.win32.sep);
  39. }
  40. line = line.trim()
  41. // at jasmine.Spec.<anonymous> (path:1:2) -> at path:1:2
  42. .replace(/^at jasmine\.Spec\.<anonymous> \(([^)]+)\)/, 'at $1')
  43. // at jasmine.Spec.it (path:1:2) -> at path:1:2
  44. .replace(/^at jasmine\.Spec\.f*it \(([^)]+)\)/, 'at $1')
  45. // at it (path:1:2) -> at path:1:2
  46. .replace(/^at f*it \(([^)]+)\)/, 'at $1')
  47. // at spec/file-test.js -> at file-test.js
  48. .replace(spec.specDirectory + path.sep, '');
  49. }
  50. return line;
  51. });
  52. return lines.join('\n').trim();
  53. };
  54. module.exports =
  55. (AtomReporter = (function() {
  56. AtomReporter = class AtomReporter {
  57. static initClass() {
  58. this.prototype.startedAt = null;
  59. this.prototype.runningSpecCount = 0;
  60. this.prototype.completeSpecCount = 0;
  61. this.prototype.passedCount = 0;
  62. this.prototype.failedCount = 0;
  63. this.prototype.skippedCount = 0;
  64. this.prototype.totalSpecCount = 0;
  65. this.prototype.deprecationCount = 0;
  66. this.timeoutId = 0;
  67. }
  68. constructor() {
  69. this.element = document.createElement('div');
  70. this.element.classList.add('spec-reporter-container');
  71. this.element.innerHTML = `\
  72. <div class="spec-reporter">
  73. <div class="padded pull-right">
  74. <button outlet="reloadButton" class="btn btn-small reload-button">Reload Specs</button>
  75. </div>
  76. <div outlet="coreArea" class="symbol-area">
  77. <div outlet="coreHeader" class="symbol-header"></div>
  78. <ul outlet="coreSummary"class="symbol-summary list-unstyled"></ul>
  79. </div>
  80. <div outlet="bundledArea" class="symbol-area">
  81. <div outlet="bundledHeader" class="symbol-header"></div>
  82. <ul outlet="bundledSummary"class="symbol-summary list-unstyled"></ul>
  83. </div>
  84. <div outlet="userArea" class="symbol-area">
  85. <div outlet="userHeader" class="symbol-header"></div>
  86. <ul outlet="userSummary"class="symbol-summary list-unstyled"></ul>
  87. </div>
  88. <div outlet="status" class="status alert alert-info">
  89. <div outlet="time" class="time"></div>
  90. <div outlet="specCount" class="spec-count"></div>
  91. <div outlet="message" class="message"></div>
  92. </div>
  93. <div outlet="results" class="results"></div>
  94. <div outlet="deprecations" class="status alert alert-warning" style="display: none">
  95. <span outlet="deprecationStatus">0 deprecations</span>
  96. <div class="deprecation-toggle"></div>
  97. </div>
  98. <div outlet="deprecationList" class="deprecation-list"></div>
  99. </div>\
  100. `;
  101. for (let element of Array.from(this.element.querySelectorAll('[outlet]'))) {
  102. this[element.getAttribute('outlet')] = element;
  103. }
  104. }
  105. reportRunnerStarting(runner) {
  106. this.handleEvents();
  107. this.startedAt = Date.now();
  108. const specs = runner.specs();
  109. this.totalSpecCount = specs.length;
  110. this.addSpecs(specs);
  111. return document.body.appendChild(this.element);
  112. }
  113. reportRunnerResults(runner) {
  114. this.updateSpecCounts();
  115. if (this.failedCount === 0) {
  116. this.status.classList.add('alert-success');
  117. this.status.classList.remove('alert-info');
  118. }
  119. if (this.failedCount === 1) {
  120. return this.message.textContent = `${this.failedCount} failure`;
  121. } else {
  122. return this.message.textContent = `${this.failedCount} failures`;
  123. }
  124. }
  125. reportSuiteResults(suite) {}
  126. reportSpecResults(spec) {
  127. this.completeSpecCount++;
  128. spec.endedAt = Date.now();
  129. this.specComplete(spec);
  130. return this.updateStatusView(spec);
  131. }
  132. reportSpecStarting(spec) {
  133. return this.specStarted(spec);
  134. }
  135. handleEvents() {
  136. listen(document, 'click', '.spec-toggle', function(event) {
  137. const specFailures = event.currentTarget.parentElement.querySelector('.spec-failures');
  138. if (specFailures.style.display === 'none') {
  139. specFailures.style.display = '';
  140. event.currentTarget.classList.remove('folded');
  141. } else {
  142. specFailures.style.display = 'none';
  143. event.currentTarget.classList.add('folded');
  144. }
  145. return event.preventDefault();
  146. });
  147. listen(document, 'click', '.deprecation-list', function(event) {
  148. const deprecationList = event.currentTarget.parentElement.querySelector('.deprecation-list');
  149. if (deprecationList.style.display === 'none') {
  150. deprecationList.style.display = '';
  151. event.currentTarget.classList.remove('folded');
  152. } else {
  153. deprecationList.style.display = 'none';
  154. event.currentTarget.classList.add('folded');
  155. }
  156. return event.preventDefault();
  157. });
  158. listen(document, 'click', '.stack-trace', event => event.currentTarget.classList.toggle('expanded'));
  159. return this.reloadButton.addEventListener('click', () => ipcHelpers.call('window-method', 'reload'));
  160. }
  161. updateSpecCounts() {
  162. let specCount;
  163. if (this.skippedCount) {
  164. specCount = `${this.completeSpecCount - this.skippedCount}/${this.totalSpecCount - this.skippedCount} (${this.skippedCount} skipped)`;
  165. } else {
  166. specCount = `${this.completeSpecCount}/${this.totalSpecCount}`;
  167. }
  168. return this.specCount.textContent = specCount;
  169. }
  170. updateStatusView(spec) {
  171. if (this.failedCount > 0) {
  172. this.status.classList.add('alert-danger');
  173. this.status.classList.remove('alert-info');
  174. }
  175. this.updateSpecCounts();
  176. let rootSuite = spec.suite;
  177. while (rootSuite.parentSuite) { rootSuite = rootSuite.parentSuite; }
  178. this.message.textContent = rootSuite.description;
  179. let time = `${Math.round((spec.endedAt - this.startedAt) / 10)}`;
  180. if (time.length < 3) { time = `0${time}`; }
  181. return this.time.textContent = `${time.slice(0, -2)}.${time.slice(-2)}s`;
  182. }
  183. specTitle(spec) {
  184. const parentDescs = [];
  185. let s = spec.suite;
  186. while (s) {
  187. parentDescs.unshift(s.description);
  188. s = s.parentSuite;
  189. }
  190. let suiteString = "";
  191. let indent = "";
  192. for (let desc of Array.from(parentDescs)) {
  193. suiteString += indent + desc + "\n";
  194. indent += " ";
  195. }
  196. return `${suiteString} ${indent} it ${spec.description}`;
  197. }
  198. addSpecs(specs) {
  199. let coreSpecs = 0;
  200. let bundledPackageSpecs = 0;
  201. let userPackageSpecs = 0;
  202. for (let spec of Array.from(specs)) {
  203. const symbol = document.createElement('li');
  204. symbol.setAttribute('id', `spec-summary-${spec.id}`);
  205. symbol.setAttribute('title', this.specTitle(spec));
  206. symbol.className = "spec-summary pending";
  207. switch (spec.specType) {
  208. case 'core':
  209. coreSpecs++;
  210. this.coreSummary.appendChild(symbol);
  211. break;
  212. case 'bundled':
  213. bundledPackageSpecs++;
  214. this.bundledSummary.appendChild(symbol);
  215. break;
  216. case 'user':
  217. userPackageSpecs++;
  218. this.userSummary.appendChild(symbol);
  219. break;
  220. }
  221. }
  222. if (coreSpecs > 0) {
  223. this.coreHeader.textContent = `Core Specs (${coreSpecs})`;
  224. } else {
  225. this.coreArea.style.display = 'none';
  226. }
  227. if (bundledPackageSpecs > 0) {
  228. this.bundledHeader.textContent = `Bundled Package Specs (${bundledPackageSpecs})`;
  229. } else {
  230. this.bundledArea.style.display = 'none';
  231. }
  232. if (userPackageSpecs > 0) {
  233. if ((coreSpecs === 0) && (bundledPackageSpecs === 0)) {
  234. // Package specs being run, show a more descriptive label
  235. const {specDirectory} = specs[0];
  236. const packageFolderName = path.basename(path.dirname(specDirectory));
  237. const packageName = _.undasherize(_.uncamelcase(packageFolderName));
  238. return this.userHeader.textContent = `${packageName} Specs`;
  239. } else {
  240. return this.userHeader.textContent = `User Package Specs (${userPackageSpecs})`;
  241. }
  242. } else {
  243. return this.userArea.style.display = 'none';
  244. }
  245. }
  246. specStarted(spec) {
  247. return this.runningSpecCount++;
  248. }
  249. specComplete(spec) {
  250. const specSummaryElement = document.getElementById(`spec-summary-${spec.id}`);
  251. specSummaryElement.classList.remove('pending');
  252. const results = spec.results();
  253. if (results.skipped) {
  254. specSummaryElement.classList.add("skipped");
  255. return this.skippedCount++;
  256. } else if (results.passed()) {
  257. specSummaryElement.classList.add("passed");
  258. return this.passedCount++;
  259. } else {
  260. specSummaryElement.classList.add("failed");
  261. const specView = new SpecResultView(spec);
  262. specView.attach();
  263. return this.failedCount++;
  264. }
  265. }
  266. };
  267. AtomReporter.initClass();
  268. return AtomReporter;
  269. })());
  270. class SuiteResultView {
  271. constructor(suite) {
  272. this.suite = suite;
  273. this.element = document.createElement('div');
  274. this.element.className = 'suite';
  275. this.element.setAttribute('id', `suite-view-${this.suite.id}`);
  276. this.description = document.createElement('div');
  277. this.description.className = 'description';
  278. this.description.textContent = this.suite.description;
  279. this.element.appendChild(this.description);
  280. }
  281. attach() {
  282. return (this.parentSuiteView() || document.querySelector('.results')).appendChild(this.element);
  283. }
  284. parentSuiteView() {
  285. let suiteViewElement;
  286. if (!this.suite.parentSuite) { return; }
  287. if (!(suiteViewElement = document.querySelector(`#suite-view-${this.suite.parentSuite.id}`))) {
  288. const suiteView = new SuiteResultView(this.suite.parentSuite);
  289. suiteView.attach();
  290. suiteViewElement = suiteView.element;
  291. }
  292. return suiteViewElement;
  293. }
  294. }
  295. class SpecResultView {
  296. constructor(spec) {
  297. this.spec = spec;
  298. this.element = document.createElement('div');
  299. this.element.className = 'spec';
  300. this.element.innerHTML = `\
  301. <div class='spec-toggle'></div>
  302. <div outlet='description' class='description'></div>
  303. <div outlet='specFailures' class='spec-failures'></div>\
  304. `;
  305. this.description = this.element.querySelector('[outlet="description"]');
  306. this.specFailures = this.element.querySelector('[outlet="specFailures"]');
  307. this.element.classList.add(`spec-view-${this.spec.id}`);
  308. let {
  309. description
  310. } = this.spec;
  311. if (description.indexOf('it ') !== 0) { description = `it ${description}`; }
  312. this.description.textContent = description;
  313. for (let result of Array.from(this.spec.results().getItems())) {
  314. if (!result.passed()) {
  315. const stackTrace = formatStackTrace(this.spec, result.message, result.trace.stack);
  316. const resultElement = document.createElement('div');
  317. resultElement.className = 'result-message fail';
  318. resultElement.textContent = result.message;
  319. this.specFailures.appendChild(resultElement);
  320. if (stackTrace) {
  321. const traceElement = document.createElement('pre');
  322. traceElement.className = 'stack-trace padded';
  323. traceElement.textContent = stackTrace;
  324. this.specFailures.appendChild(traceElement);
  325. }
  326. }
  327. }
  328. }
  329. attach() {
  330. return this.parentSuiteView().appendChild(this.element);
  331. }
  332. parentSuiteView() {
  333. let suiteViewElement;
  334. if (!(suiteViewElement = document.querySelector(`#suite-view-${this.spec.suite.id}`))) {
  335. const suiteView = new SuiteResultView(this.spec.suite);
  336. suiteView.attach();
  337. suiteViewElement = suiteView.element;
  338. }
  339. return suiteViewElement;
  340. }
  341. }
  342. function __guard__(value, transform) {
  343. return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
  344. }