1 | "use strict";
|
2 |
|
3 | const fs = require("fs");
|
4 | const path = require("path");
|
5 | const diff = require("diff");
|
6 | const print = require("print");
|
7 | const ipc = require("./ipc.js");
|
8 | const utils = require("./utils.js");
|
9 | const painter = require("./patch-painter.js");
|
10 | const {clean} = require("mocha/lib/utils.js");
|
11 | const {addTo, nearest, link, New, escapeRegExp, escapeHTML, parseKeywords} = utils;
|
12 |
|
13 | const cssPath = require.resolve("./reporter.css");
|
14 | const el = Symbol("Atom-Mocha-Elements");
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | class Reporter{
|
23 |
|
24 | constructor(runner, options){
|
25 | this.runner = runner;
|
26 | this.options = options;
|
27 |
|
28 |
|
29 | const events = {
|
30 | start: this.onStart,
|
31 | suite: this.onSuite,
|
32 | pass: this.onPass,
|
33 | fail: this.onFail,
|
34 | end: this.onEnd,
|
35 | pending: this.onPending,
|
36 | "test end": this.onTestEnd
|
37 | };
|
38 | for(const event in events)
|
39 | runner.on(event, events[event].bind(this));
|
40 |
|
41 |
|
42 | this.element = New("div", {id: "mocha"});
|
43 | this.report = New("div", {id: "mocha-report"});
|
44 | this.element.appendChild(this.report);
|
45 | this.element.addEventListener("mousedown", this.onClick);
|
46 |
|
47 |
|
48 | this.statBar = New("footer", {id: "mocha-stats"});
|
49 | this.element.appendChild(addTo(this.statBar)(
|
50 | this.statBar.passes = New("div", {textContent: "0", id: "mocha-passes"}),
|
51 | this.statBar.duration = this.createDuration(...[,,{tagName: "div", id:"mocha-duration"}]),
|
52 | this.statBar.pending = New("div", {textContent: "0", id: "mocha-pending"}),
|
53 | this.statBar.failures = New("div", {textContent: "0", id: "mocha-failures"})
|
54 | )[0]);
|
55 |
|
56 |
|
57 | if(this.scrollbarWidth = utils.getScrollbarWidth()){
|
58 | const offset = this.scrollbarWidth + "px";
|
59 | this.report.style.marginRight = "-" + offset;
|
60 | this.report.style.paddingRight = offset;
|
61 | }
|
62 |
|
63 | document.title = options.title || "Mocha";
|
64 | document.body.classList.add("hide", "native-key-bindings");
|
65 | document.body.appendChild(this.element);
|
66 | document.head.appendChild(New("link", {
|
67 | rel: "stylesheet",
|
68 | type: "text/css",
|
69 | href: "file://" + cssPath
|
70 | }));
|
71 |
|
72 |
|
73 | const ed = Reporter.editorSettings || {};
|
74 | const rules = [
|
75 | `background-color: rgba(0,0,0,${options.opacity || .8})`,
|
76 | ed.fontSize ? `font-size: ${ed.fontSize}px` : null,
|
77 | ed.lineHeight ? `line-height: ${ed.lineHeight}` : null,
|
78 | ed.fontFamily ? `font-family: "${ed.fontFamily}"` : null,
|
79 | ed.tabLength ? `tab-size: ${ed.tabLength}` : null
|
80 | ].filter(Boolean).map(s => `\t${s};`);
|
81 | document.head.appendChild(New("style", {
|
82 | textContent: `#mocha{\n${ rules.join("\n") }\n}`
|
83 | }));
|
84 |
|
85 |
|
86 | let title = "";
|
87 | Object.defineProperty(document, "title", {
|
88 | get(){ return title },
|
89 | set(i){ title = i; }
|
90 | });
|
91 |
|
92 |
|
93 | if(options.slide){
|
94 | const dir = parseKeywords(true === options.slide
|
95 | ? "up down left right"
|
96 | : options.slide);
|
97 |
|
98 | this.element.classList.add("sliding");
|
99 | for(const key of Object.keys(dir)){
|
100 |
|
101 |
|
102 | const arrow = New("div", {
|
103 | direction: key,
|
104 | className: "slide-indicator " + key
|
105 | });
|
106 |
|
107 | arrow.addEventListener("click", e => {
|
108 | this.element.classList.toggle("offset-" + key);
|
109 | e.preventDefault();
|
110 | e.stopImmediatePropagation();
|
111 | return false;
|
112 | });
|
113 |
|
114 | addTo(this.element)(arrow);
|
115 | this.element.classList.add("slide-" + key);
|
116 | }
|
117 | }
|
118 |
|
119 |
|
120 | this.element.classList.toggle("hide-stats", !!options.hideStatBar);
|
121 |
|
122 |
|
123 | if(options.minimal){
|
124 | this.minimal = true;
|
125 | this.dots = New("div", {id: "mocha-dots"});
|
126 | this.element.classList.add("minimal");
|
127 | this.element.insertBefore(this.dots, this.report);
|
128 | this.element.insertBefore(this.statBar, this.report);
|
129 | }
|
130 |
|
131 |
|
132 | if(options.linkPaths)
|
133 | ipc.init(options.packagePath);
|
134 | }
|
135 |
|
136 |
|
137 | get passes(){ return +this.statBar.passes.dataset.value || 0 }
|
138 | set passes(input){
|
139 | input = Math.max(0, +input || 0);
|
140 | const el = this.statBar.passes;
|
141 | el.textContent = `${input} passing`;
|
142 | el.dataset.value = input;
|
143 | el.classList.toggle("zero", input === 0);
|
144 | }
|
145 |
|
146 | get pending(){ return +this.statBar.pending.dataset.value || 0 }
|
147 | set pending(input){
|
148 | input = Math.max(0, +input || 0);
|
149 | const el = this.statBar.pending;
|
150 | el.textContent = `${input} pending`;
|
151 | el.dataset.value = input;
|
152 | el.classList.toggle("zero", input === 0);
|
153 | }
|
154 |
|
155 | get failures(){ return +this.statBar.failures.dataset.value || 0 }
|
156 | set failures(input){
|
157 | input = Math.max(0, +input || 0);
|
158 | const el = this.statBar.failures;
|
159 | el.textContent = `${input} failing`;
|
160 | el.dataset.value = input;
|
161 | el.classList.toggle("zero", input === 0);
|
162 | }
|
163 |
|
164 | get duration(){ return this.statBar.duration || 0 }
|
165 | set duration(input){
|
166 | const el = this.statBar.duration;
|
167 | el.value = input;
|
168 | }
|
169 |
|
170 |
|
171 | onStart(){
|
172 | this.passes = 0;
|
173 | this.failures = 0;
|
174 | this.total = 0;
|
175 | this.pending = 0;
|
176 | this.started = performance.now();
|
177 | setTimeout(() => {
|
178 | document.body.classList.remove("hide");
|
179 | }, 10);
|
180 | }
|
181 |
|
182 |
|
183 | onSuite(suite){
|
184 |
|
185 |
|
186 | if(!suite[el] && !suite.root)
|
187 | this.addSuite(suite);
|
188 | }
|
189 |
|
190 |
|
191 | onTestEnd(){
|
192 | ++this.total;
|
193 | this.duration = performance.now() - this.started;
|
194 | }
|
195 |
|
196 |
|
197 | onPass(test){
|
198 | ++this.passes;
|
199 | this.addResult(test);
|
200 | }
|
201 |
|
202 |
|
203 | onFail(test, reason){
|
204 | ++this.failures;
|
205 | this.addResult(test, reason);
|
206 | }
|
207 |
|
208 |
|
209 | onPending(test){
|
210 | if(this.options.hidePending) return;
|
211 | ++this.pending;
|
212 | this.addResult(test);
|
213 | }
|
214 |
|
215 |
|
216 | onEnd(){
|
217 | this.finished = performance.now();
|
218 | this.duration = this.finished - this.started;
|
219 |
|
220 | if(!this.summary){
|
221 | const total = this.runner.total - this.total;
|
222 | const textContent = total
|
223 | ? this.options.bail
|
224 | ? `Bailed with ${total} tests remaining`.replace(/\b(1 test)s/, "$1")
|
225 | : `Finished with ${total} aborted tests`.replace(/\b(1 aborted test)s/, "$1")
|
226 | : "Finished";
|
227 | this.summary = addTo(this.report)(New("div", {textContent}))[1];
|
228 | if(this.options.autoScroll)
|
229 | this.report.scrollTop = this.report.scrollHeight;
|
230 | }
|
231 | }
|
232 |
|
233 |
|
234 |
|
235 | |
236 |
|
237 |
|
238 |
|
239 |
|
240 | onClick(event){
|
241 | const hideClass = "collapsed";
|
242 | const {target, altKey} = event;
|
243 | let title, node, parent, isClosed, siblings;
|
244 |
|
245 |
|
246 | if(node = nearest(target, ".stack-source.link[data-path]")){
|
247 | const {path, line, column} = node.dataset;
|
248 | ipc.jumpToFile(path, line - 1, column - 1);
|
249 | }
|
250 |
|
251 |
|
252 | if(title = nearest(target, ".test-title")){
|
253 | parent = title[el].container;
|
254 | isClosed = parent.classList.toggle(hideClass);
|
255 |
|
256 | if(altKey){
|
257 | for(const e of Array.from(parent.parentElement.children)){
|
258 | if(!e[el] || !e[el].details) continue;
|
259 | e.classList.toggle(hideClass, isClosed);
|
260 | }
|
261 | }
|
262 | }
|
263 |
|
264 |
|
265 | else if(title = nearest(target, ".suite-title")){
|
266 | parent = title.parentNode;
|
267 | isClosed = !parent.classList.toggle(hideClass);
|
268 |
|
269 | if(altKey){
|
270 | siblings = Array.from(parent[el].parent.children);
|
271 | siblings = siblings.filter(e => e.classList.contains("suite"));
|
272 |
|
273 | for(const e of siblings){
|
274 | if(e !== parent)
|
275 | e.classList.toggle(hideClass, !isClosed);
|
276 |
|
277 |
|
278 | for(const s of Array.from(e.querySelectorAll(".suite, .test")))
|
279 | s.classList.toggle(hideClass, !isClosed);
|
280 | }
|
281 | }
|
282 | }
|
283 |
|
284 |
|
285 | if(node){
|
286 | event.preventDefault();
|
287 | event.stopPropagation();
|
288 | return false;
|
289 | }
|
290 | }
|
291 |
|
292 |
|
293 | |
294 |
|
295 |
|
296 |
|
297 |
|
298 | addSuite(suite){
|
299 | const container = New("article", {className: "suite"});
|
300 | const title = New("h1", {className: "suite-title", textContent: suite.title});
|
301 | const results = New("div", {className: "results"});
|
302 |
|
303 | container.appendChild(title);
|
304 | container.appendChild(results);
|
305 | const nodeRefs = {suite, container, title, results};
|
306 |
|
307 | const parent = suite.parent[el];
|
308 | const parentEl = parent ? parent.results : this.report;
|
309 | nodeRefs.parent = addTo(parentEl)(container)[0];
|
310 | link(el, nodeRefs);
|
311 | }
|
312 |
|
313 |
|
314 | |
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 | addResult(test, error){
|
321 | const speed = this.rankSpeed(test);
|
322 | const state = error ? "fail" : (test.pending ? "pending" : "pass");
|
323 | const show = error ? "" : " collapsed";
|
324 |
|
325 | const prefix = this.options.autoIt ? (test.type === "hook" ? "In " : "It ") : "";
|
326 | const container = New("div", {className: `test ${state} ${speed}` + show});
|
327 | const title = New("div", {className: "test-title"});
|
328 | const h2 = New("h2", {textContent: prefix + test.title});
|
329 | addTo(title)(h2);
|
330 |
|
331 | if(!test.pending){
|
332 | const duration = this.createDuration(test.duration, true);
|
333 | duration.title += ` (${speed === "okay" ? "medium" : speed})`;
|
334 | addTo(title)(duration);
|
335 | }
|
336 |
|
337 | container.appendChild(title);
|
338 | const details = (error
|
339 | ? addTo(container)(this.createErrorBlock(error))
|
340 | : test.pending || addTo(container)(this.createCodeBlock(test.body)))[1];
|
341 | test.parent[el].results.appendChild(container);
|
342 | link(el, {container, title, details});
|
343 | error && test.parent[el].container.classList.add("has-failures");
|
344 |
|
345 | if(this.dots){
|
346 | const dot = New("div", {className: container.className.replace(/test/, "dot")});
|
347 | addTo(this.dots)(dot);
|
348 | }
|
349 |
|
350 | if(this.options.autoScroll)
|
351 | this.report.scrollTop = this.report.scrollHeight;
|
352 | }
|
353 |
|
354 |
|
355 | |
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 | createErrorBlock(error){
|
362 | const div = New("div", {error, className: "error-block"});
|
363 | const title = New("div", {error, className: "error-title"});
|
364 | const stack = New("div", {error, className: "stack-trace"});
|
365 | let diff;
|
366 |
|
367 |
|
368 | if(!error.rawStack){
|
369 | const uncaught = error.uncaught? "Uncaught " : "";
|
370 | const titleText = `${uncaught + error.name}: `;
|
371 | title.innerHTML = this.options.escapeHTML
|
372 | ? escapeHTML(titleText)
|
373 | : titleText;
|
374 |
|
375 |
|
376 | if(error.message && "function" === typeof error.message.toString)
|
377 | stack.textContent = error.message;
|
378 | else if("function" === typeof error.inspect)
|
379 | stack.textContent = error.inspect();
|
380 | }
|
381 |
|
382 |
|
383 | else{
|
384 | const [titleText, stackData] = this.formatStackTrace(error);
|
385 | title.innerHTML = titleText.replace(/[{}:()\]\[]+/g, s =>
|
386 | `<span class="stack-trace">${s}</span>`);
|
387 |
|
388 | for(const [html, callSite] of stackData){
|
389 | const frame = New("span", {
|
390 | className: "stack-frame",
|
391 | innerHTML: html + "\n"
|
392 | });
|
393 | const {textContent} = frame;
|
394 | if(this.options.stackFilter.test(textContent))
|
395 | frame.hidden = true;
|
396 | stack.appendChild(Object.assign(frame, {error, callSite}));
|
397 | }
|
398 | if(error.showDiff && (diff = this.createDiffBlock(error)))
|
399 | div.classList.add("has-diff");
|
400 | }
|
401 |
|
402 | return addTo(div)(title, diff, stack)[0];
|
403 | }
|
404 |
|
405 |
|
406 |
|
407 | |
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 | createCodeBlock(src){
|
414 | const pre = New("pre", {className: "code-block"});
|
415 | const code = New("code", {textContent: src});
|
416 |
|
417 | if(this.options.formatCode){
|
418 | code.textContent = "";
|
419 | src = clean(src).replace(/^(\x20{2})+/gm, s => "\t".repeat(s.length / 2));
|
420 | for(let node of this.highlight(src))
|
421 | code.appendChild(node);
|
422 | }
|
423 |
|
424 | pre.appendChild(code);
|
425 | return pre;
|
426 | }
|
427 |
|
428 |
|
429 |
|
430 | |
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 | createDiffBlock(error){
|
439 | if(!error || !("expected" in error && "actual" in error))
|
440 | return null;
|
441 |
|
442 | const div = New("div", {className: "error-diff"});
|
443 |
|
444 | const legend = addTo(
|
445 | New("div", {className: "diff-legend"})
|
446 | )(
|
447 | New("span", {className: "diff-expected", textContent: "+ expected"}),
|
448 | New("span", {className: "diff-actual", textContent: "- actual"})
|
449 | )[0];
|
450 |
|
451 |
|
452 | const patch = diff.createPatch("--",
|
453 | print(error.actual),
|
454 | print(error.expected)
|
455 | );
|
456 |
|
457 | const details = New("div", {
|
458 | className: "diff-details",
|
459 | innerHTML: painter.html(patch)
|
460 | });
|
461 | return addTo(div)(legend, details)[0];
|
462 | }
|
463 |
|
464 |
|
465 |
|
466 | |
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 | createDuration(input = 0, noUnits = false, attr = {}){
|
475 | const attrib = Object.assign({}, attr, {className: "duration"});
|
476 | const tagName = attr.tagName || "span";
|
477 | delete attrib.tagName;
|
478 |
|
479 | const span = New(tagName, attrib);
|
480 | const data = New("data", {className: "amount"});
|
481 | const unit = New("span", {className: "unit"});
|
482 |
|
483 | Object.defineProperty(span, "value", {
|
484 | get(){ return +data.value || 0; },
|
485 |
|
486 | set(input){
|
487 | input = Math.max(0, +input || 0);
|
488 | data.value = input;
|
489 |
|
490 |
|
491 | if(noUnits || input < 1000){
|
492 | input = parseFloat(input.toFixed(2));
|
493 | span.title = `${input} millisecond${input==1 ? "":"s"}`;
|
494 | data.textContent = input;
|
495 | unit.textContent = "ms";
|
496 | }
|
497 |
|
498 | else if(input >= 60000){
|
499 | input = parseFloat((input / 60000).toFixed(2));
|
500 | span.title = `${input} minute${input==1 ? "":"s"}`;
|
501 | data.textContent = input;
|
502 | unit.textContent = "m";
|
503 | }
|
504 |
|
505 | else{
|
506 | input = parseFloat((input / 1000).toFixed(2));
|
507 | span.title = `${input} second${input==1 ? "":"s"}`;
|
508 | data.textContent = input;
|
509 | unit.textContent = "s";
|
510 | }
|
511 | }
|
512 | });
|
513 |
|
514 | span.value = input;
|
515 | span.appendChild(data);
|
516 | span.appendChild(unit);
|
517 | return span;
|
518 | }
|
519 |
|
520 |
|
521 |
|
522 | |
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 | rankSpeed(test){
|
529 | const thresh = test.slow();
|
530 | if(test.duration > thresh) return "slow";
|
531 | if(test.duration > thresh / 2) return "okay";
|
532 | return "fast";
|
533 | }
|
534 |
|
535 |
|
536 | |
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 | formatStackTrace(error){
|
544 | let title = error.toString();
|
545 | let stack = error.rawStack.slice();
|
546 |
|
547 |
|
548 | if(this.options.flipStack)
|
549 | stack.reverse();
|
550 |
|
551 | const frames = stack.map(frame => {
|
552 | const html = this.formatStackFrame(frame);
|
553 | return [html, frame];
|
554 | });
|
555 | return [title, frames];
|
556 | }
|
557 |
|
558 |
|
559 | |
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 | formatStackFrame(frame){
|
566 | const result = ["at "];
|
567 | const anon = "<anonymous>";
|
568 |
|
569 |
|
570 | const type = frame.getTypeName();
|
571 | const method = frame.getMethodName();
|
572 | const name = frame.getFunctionName();
|
573 | const callee = frame.isConstructor()
|
574 | ? "new " + (type || anon)
|
575 | : (type ? type + "." : "")
|
576 | + (name || method || anon);
|
577 | result.push('<span class="stack-callee">'
|
578 | + callee.replace(/(^new )?((?:[$\w]+\.)*<anonymous>)/, (_,a,b) =>
|
579 | `${a || ""}<span class="stack-trace">${escapeHTML(b)}</span>`)
|
580 | + "</span>");
|
581 |
|
582 |
|
583 | if(method && method !== name)
|
584 | result.push(`<span class="stack-notes"> [as ${escapeHTML(method)}]</span>`);
|
585 |
|
586 |
|
587 | let file = frame.getScriptNameOrSourceURL();
|
588 | if(file){
|
589 | const row = frame.getLineNumber();
|
590 | const col = frame.getColumnNumber();
|
591 | const escapedPath = escapeRegExp(this.options.packagePath + path.sep);
|
592 | const pathPattern = new RegExp("^" + escapedPath);
|
593 | const openingTag = this.options.linkPaths && pathPattern.test(file)
|
594 | ? `<span class="stack-source link" data-line=${row} data-column=${col} data-path="${file}">`
|
595 | : '<span class="stack-source">';
|
596 |
|
597 | if(this.options.clipPaths)
|
598 | file = file.replace(pathPattern, "");
|
599 |
|
600 | file = escapeHTML(file);
|
601 | result.push(` ${openingTag}(`
|
602 | + '<span class="stack-filename">' + file + "</span>:"
|
603 | + '<span class="stack-line">' + row + "</span>:"
|
604 | + '<span class="stack-column">' + col + "</span>"
|
605 | + ")</span>");
|
606 | }
|
607 |
|
608 |
|
609 | else{
|
610 | result.push('<span class="stack-source other">(');
|
611 | if (frame.isNative()) result.push("native");
|
612 | else if (frame.isEval()) result.push("eval at " + escapeHTML(frame.getPosition()));
|
613 | result.push(")</span>");
|
614 | }
|
615 |
|
616 | return result.join("");
|
617 | }
|
618 |
|
619 |
|
620 | |
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 | highlight(input, atom){
|
628 | const {grammars} = Reporter;
|
629 | const nodes = [];
|
630 |
|
631 |
|
632 | if(grammars === undefined)
|
633 | return nodes;
|
634 |
|
635 | for(let line of grammars["source.js"].tokenizeLines(input)){
|
636 | for(let token of line){
|
637 | const tag = document.createElement("span");
|
638 | tag.textContent = token.value;
|
639 |
|
640 | const scopes = token.scopes.join(".").split(/\./g);
|
641 | const s = parseKeywords(scopes);
|
642 | tag.dataset.scopes = Array.from(new Set(scopes).values()).join(" ");
|
643 |
|
644 |
|
645 | if(s.punctuation) tag.className = s.string ? "js-quote" : "js-punct";
|
646 | else if(s.meta && s.brace || s.delimiter) tag.className = "js-punct";
|
647 | else if(s.keyword || s.entity && s.name) tag.className = "js-key";
|
648 | else if(s.function || s.storage) tag.className = "js-key";
|
649 | else if(s.constant || s.string) tag.className = "js-value";
|
650 | else if(s.property || s.variable) tag.className = "js-ident";
|
651 | else tag.className = "js";
|
652 |
|
653 | nodes.push(tag);
|
654 | }
|
655 | nodes.push(document.createTextNode("\n"));
|
656 | }
|
657 | return nodes;
|
658 | }
|
659 |
|
660 |
|
661 | |
662 |
|
663 |
|
664 |
|
665 |
|
666 |
|
667 | static beforeStart(main){
|
668 | const promises = [this.loadEditorSettings()];
|
669 |
|
670 |
|
671 | if(main.options.formatCode){
|
672 | this.grammars = {};
|
673 |
|
674 | promises.push(atom.packages.activatePackage("language-javascript").then(p => {
|
675 | p.grammars.forEach(g => this.grammars[g.scopeName] = g);
|
676 |
|
677 |
|
678 | atom.packages.deactivatePackage("language-javascript");
|
679 | }));
|
680 | }
|
681 |
|
682 | return Promise.all(promises);
|
683 | }
|
684 |
|
685 |
|
686 |
|
687 | |
688 |
|
689 |
|
690 |
|
691 |
|
692 |
|
693 |
|
694 |
|
695 | static loadEditorSettings(){
|
696 |
|
697 | return new Promise((resolve, reject) => {
|
698 | const configPath = atom.config.configFilePath;
|
699 |
|
700 | fs.readFile(configPath, (error, data) => {
|
701 | if(error) return reject(error);
|
702 |
|
703 |
|
704 | const scopes = {};
|
705 | data.toString().split(/^(?=\S)/gm).forEach(s => {
|
706 | const scope = /^(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\w+)/;
|
707 | const editor = /\n {2}editor:\n[^\x00]+?\n(?: {2}\S|$)/;
|
708 | const ed = (s.match(editor) || [""])[0];
|
709 |
|
710 |
|
711 | if(s = (s.match(scope) || [""])[0].replace(/["']/g, ""))
|
712 | scopes[s] = {
|
713 | fontFamily: (ed.match(/^ +fontFamily:\s*("|')(.+)\1/im) || []).pop(),
|
714 | fontSize: (ed.match(/^ +fontSize:\s*([\d.]+)/im) || []).pop(),
|
715 | lineHeight: (ed.match(/^ +lineHeight:\s*([\d.]+)/im) || []).pop(),
|
716 | tabLength: (ed.match(/^ +tabLength:\s*(\d+)/im) || []).pop()
|
717 | };
|
718 | });
|
719 |
|
720 |
|
721 | const ed = {};
|
722 | for(let scope of ["*", ".atom-mocha"]){
|
723 | for(let k in scopes[scope]){
|
724 | let v = scopes[scope][k]
|
725 | if(v != null) ed[k] = v
|
726 | }
|
727 | }
|
728 | Reporter.editorSettings = ed;
|
729 | resolve();
|
730 | });
|
731 | });
|
732 | }
|
733 | }
|
734 |
|
735 |
|
736 | module.exports = Reporter;
|