ava 2.0.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint-plugin-helper.js +26 -7
- package/index.d.ts +96 -23
- package/lib/api.js +17 -3
- package/lib/assert.js +21 -25
- package/lib/babel-pipeline.js +27 -15
- package/lib/cli.js +32 -5
- package/lib/fork.js +1 -0
- package/lib/globs.js +4 -4
- package/lib/load-config.js +41 -13
- package/lib/parse-test-args.js +15 -0
- package/lib/reporters/mini.js +5 -0
- package/lib/reporters/tap.js +23 -4
- package/lib/reporters/verbose.js +3 -0
- package/lib/runner.js +29 -27
- package/lib/serialize-error.js +7 -2
- package/lib/snapshot-manager.js +39 -20
- package/lib/test.js +236 -31
- package/lib/watcher.js +5 -4
- package/lib/worker/fake-tty-has-colors.js +19 -0
- package/lib/worker/fake-tty.js +59 -13
- package/lib/worker/subprocess.js +1 -0
- package/package.json +52 -45
- package/profile.js +8 -5
- package/readme.md +4 -2
package/lib/test.js
CHANGED
|
@@ -6,6 +6,7 @@ const isObservable = require('is-observable');
|
|
|
6
6
|
const plur = require('plur');
|
|
7
7
|
const assert = require('./assert');
|
|
8
8
|
const nowAndTimers = require('./now-and-timers');
|
|
9
|
+
const parseTestArgs = require('./parse-test-args');
|
|
9
10
|
const concordanceOptions = require('./concordance-options').default;
|
|
10
11
|
|
|
11
12
|
function formatErrorValue(label, error) {
|
|
@@ -13,13 +14,12 @@ function formatErrorValue(label, error) {
|
|
|
13
14
|
return {label, formatted};
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const captureSavedError = () => {
|
|
17
18
|
const limitBefore = Error.stackTraceLimit;
|
|
18
19
|
Error.stackTraceLimit = 1;
|
|
19
|
-
const
|
|
20
|
-
Error.captureStackTrace(obj, start);
|
|
20
|
+
const err = new Error();
|
|
21
21
|
Error.stackTraceLimit = limitBefore;
|
|
22
|
-
return
|
|
22
|
+
return err;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const testMap = new WeakMap();
|
|
@@ -60,7 +60,7 @@ class ExecutionContext extends assert.Assertions {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
this.plan = count => {
|
|
63
|
-
test.plan(count,
|
|
63
|
+
test.plan(count, captureSavedError());
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
this.plan.skip = () => {};
|
|
@@ -68,11 +68,100 @@ class ExecutionContext extends assert.Assertions {
|
|
|
68
68
|
this.timeout = ms => {
|
|
69
69
|
test.timeout(ms);
|
|
70
70
|
};
|
|
71
|
+
|
|
72
|
+
this.try = async (...attemptArgs) => {
|
|
73
|
+
if (test.experiments.tryAssertion !== true) {
|
|
74
|
+
throw new Error('t.try() is currently an experiment. Opt in by setting `nonSemVerExperiments.tryAssertion` to `true`.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs);
|
|
78
|
+
|
|
79
|
+
if (implementations.length === 0) {
|
|
80
|
+
throw new TypeError('Expected an implementation.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const attemptPromises = implementations.map(implementation => {
|
|
84
|
+
let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
|
|
85
|
+
|
|
86
|
+
if (!isSet || isEmpty) {
|
|
87
|
+
title = `${test.title} (attempt ${test.attemptCount + 1})`;
|
|
88
|
+
} else if (!isValid) {
|
|
89
|
+
throw new TypeError('`t.try()` titles must be strings'); // Throw synchronously!
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!test.registerUniqueTitle(title)) {
|
|
93
|
+
throw new Error(`Duplicate test title: ${title}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {implementation, title};
|
|
97
|
+
}).map(async ({implementation, title}) => {
|
|
98
|
+
let committed = false;
|
|
99
|
+
let discarded = false;
|
|
100
|
+
|
|
101
|
+
const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(title, t => implementation(t, ...args));
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
errors,
|
|
105
|
+
logs: [...logs], // Don't allow modification of logs.
|
|
106
|
+
passed,
|
|
107
|
+
title,
|
|
108
|
+
commit: ({retainLogs = true} = {}) => {
|
|
109
|
+
if (committed) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (discarded) {
|
|
114
|
+
test.saveFirstError(new Error('Can\'t commit a result that was previously discarded'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
committed = true;
|
|
119
|
+
test.finishAttempt({
|
|
120
|
+
assertCount,
|
|
121
|
+
commit: true,
|
|
122
|
+
deferredSnapshotRecordings,
|
|
123
|
+
errors,
|
|
124
|
+
logs,
|
|
125
|
+
passed,
|
|
126
|
+
retainLogs,
|
|
127
|
+
snapshotCount,
|
|
128
|
+
startingSnapshotCount
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
discard: ({retainLogs = false} = {}) => {
|
|
132
|
+
if (committed) {
|
|
133
|
+
test.saveFirstError(new Error('Can\'t discard a result that was previously committed'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (discarded) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
discarded = true;
|
|
142
|
+
test.finishAttempt({
|
|
143
|
+
assertCount: 0,
|
|
144
|
+
commit: false,
|
|
145
|
+
deferredSnapshotRecordings,
|
|
146
|
+
errors,
|
|
147
|
+
logs,
|
|
148
|
+
passed,
|
|
149
|
+
retainLogs,
|
|
150
|
+
snapshotCount,
|
|
151
|
+
startingSnapshotCount
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const results = await Promise.all(attemptPromises);
|
|
158
|
+
return receivedImplementationArray ? results : results[0];
|
|
159
|
+
};
|
|
71
160
|
}
|
|
72
161
|
|
|
73
162
|
get end() {
|
|
74
163
|
const end = testMap.get(this).bindEndCallback();
|
|
75
|
-
const endFn = error => end(error,
|
|
164
|
+
const endFn = error => end(error, captureSavedError());
|
|
76
165
|
return endFn;
|
|
77
166
|
}
|
|
78
167
|
|
|
@@ -100,32 +189,74 @@ class ExecutionContext extends assert.Assertions {
|
|
|
100
189
|
class Test {
|
|
101
190
|
constructor(options) {
|
|
102
191
|
this.contextRef = options.contextRef;
|
|
192
|
+
this.experiments = options.experiments || {};
|
|
103
193
|
this.failWithoutAssertions = options.failWithoutAssertions;
|
|
104
194
|
this.fn = options.fn;
|
|
105
195
|
this.metadata = options.metadata;
|
|
106
196
|
this.title = options.title;
|
|
197
|
+
this.registerUniqueTitle = options.registerUniqueTitle;
|
|
107
198
|
this.logs = [];
|
|
108
199
|
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
200
|
+
const {snapshotBelongsTo = this.title, nextSnapshotIndex = 0} = options;
|
|
201
|
+
this.snapshotBelongsTo = snapshotBelongsTo;
|
|
202
|
+
this.nextSnapshotIndex = nextSnapshotIndex;
|
|
203
|
+
this.snapshotCount = 0;
|
|
204
|
+
|
|
205
|
+
const deferRecording = this.metadata.inline;
|
|
206
|
+
this.deferredSnapshotRecordings = [];
|
|
207
|
+
this.compareWithSnapshot = ({expected, id, message}) => {
|
|
208
|
+
this.snapshotCount++;
|
|
209
|
+
|
|
210
|
+
// TODO: In a breaking change, reject non-undefined, falsy IDs and messages.
|
|
211
|
+
const belongsTo = id || snapshotBelongsTo;
|
|
212
|
+
const index = id ? 0 : this.nextSnapshotIndex++;
|
|
213
|
+
const label = id ? '' : message || `Snapshot ${index + 1}`; // Human-readable labels start counting at 1.
|
|
214
|
+
|
|
215
|
+
const {record, ...result} = options.compareTestSnapshot({belongsTo, deferRecording, expected, index, label});
|
|
216
|
+
if (record) {
|
|
217
|
+
this.deferredSnapshotRecordings.push(record);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
116
221
|
};
|
|
117
222
|
|
|
118
223
|
this.skipSnapshot = () => {
|
|
119
224
|
if (options.updateSnapshots) {
|
|
120
225
|
this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots'));
|
|
121
226
|
} else {
|
|
122
|
-
this.
|
|
227
|
+
this.nextSnapshotIndex++;
|
|
228
|
+
this.snapshotCount++;
|
|
123
229
|
this.countPassedAssertion();
|
|
124
230
|
}
|
|
125
231
|
};
|
|
126
232
|
|
|
233
|
+
this.runAttempt = async (title, fn) => {
|
|
234
|
+
if (this.finishing) {
|
|
235
|
+
this.saveFirstError(new Error('Running a `t.try()`, but the test has already finished'));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.attemptCount++;
|
|
239
|
+
this.pendingAttemptCount++;
|
|
240
|
+
|
|
241
|
+
const {contextRef, snapshotBelongsTo, nextSnapshotIndex, snapshotCount: startingSnapshotCount} = this;
|
|
242
|
+
const attempt = new Test({
|
|
243
|
+
...options,
|
|
244
|
+
fn,
|
|
245
|
+
metadata: {...options.metadata, callback: false, failing: false, inline: true},
|
|
246
|
+
contextRef: contextRef.copy(),
|
|
247
|
+
snapshotBelongsTo,
|
|
248
|
+
nextSnapshotIndex,
|
|
249
|
+
title
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const {deferredSnapshotRecordings, error, logs, passed, assertCount, snapshotCount} = await attempt.run();
|
|
253
|
+
const errors = error ? [error] : [];
|
|
254
|
+
return {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount};
|
|
255
|
+
};
|
|
256
|
+
|
|
127
257
|
this.assertCount = 0;
|
|
128
258
|
this.assertError = undefined;
|
|
259
|
+
this.attemptCount = 0;
|
|
129
260
|
this.calledEnd = false;
|
|
130
261
|
this.duration = null;
|
|
131
262
|
this.endCallbackFinisher = null;
|
|
@@ -134,24 +265,29 @@ class Test {
|
|
|
134
265
|
this.finishDueToTimeout = null;
|
|
135
266
|
this.finishing = false;
|
|
136
267
|
this.pendingAssertionCount = 0;
|
|
268
|
+
this.pendingAttemptCount = 0;
|
|
137
269
|
this.pendingThrowsAssertion = null;
|
|
138
270
|
this.planCount = null;
|
|
139
271
|
this.startedAt = 0;
|
|
140
|
-
this.timeoutTimer = null;
|
|
141
272
|
this.timeoutMs = 0;
|
|
273
|
+
this.timeoutTimer = null;
|
|
142
274
|
}
|
|
143
275
|
|
|
144
276
|
bindEndCallback() {
|
|
145
277
|
if (this.metadata.callback) {
|
|
146
|
-
return (error,
|
|
147
|
-
this.endCallback(error,
|
|
278
|
+
return (error, savedError) => {
|
|
279
|
+
this.endCallback(error, savedError);
|
|
148
280
|
};
|
|
149
281
|
}
|
|
150
282
|
|
|
151
|
-
|
|
283
|
+
if (this.metadata.inline) {
|
|
284
|
+
throw new Error('`t.end()` is not supported inside `t.try()`');
|
|
285
|
+
} else {
|
|
286
|
+
throw new Error('`t.end()` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`');
|
|
287
|
+
}
|
|
152
288
|
}
|
|
153
289
|
|
|
154
|
-
endCallback(error,
|
|
290
|
+
endCallback(error, savedError) {
|
|
155
291
|
if (this.calledEnd) {
|
|
156
292
|
this.saveFirstError(new Error('`t.end()` called more than once'));
|
|
157
293
|
return;
|
|
@@ -163,7 +299,7 @@ class Test {
|
|
|
163
299
|
this.saveFirstError(new assert.AssertionError({
|
|
164
300
|
actual: error,
|
|
165
301
|
message: 'Callback called with an error',
|
|
166
|
-
|
|
302
|
+
savedError,
|
|
167
303
|
values: [formatErrorValue('Callback called with an error:', error)]
|
|
168
304
|
}));
|
|
169
305
|
}
|
|
@@ -182,6 +318,10 @@ class Test {
|
|
|
182
318
|
this.saveFirstError(new Error('Assertion passed, but test has already finished'));
|
|
183
319
|
}
|
|
184
320
|
|
|
321
|
+
if (this.pendingAttemptCount > 0) {
|
|
322
|
+
this.saveFirstError(new Error('Assertion passed, but an attempt is pending. Use the attempt’s assertions instead'));
|
|
323
|
+
}
|
|
324
|
+
|
|
185
325
|
this.assertCount++;
|
|
186
326
|
this.refreshTimeout();
|
|
187
327
|
}
|
|
@@ -192,7 +332,11 @@ class Test {
|
|
|
192
332
|
|
|
193
333
|
addPendingAssertion(promise) {
|
|
194
334
|
if (this.finishing) {
|
|
195
|
-
this.saveFirstError(new Error('Assertion
|
|
335
|
+
this.saveFirstError(new Error('Assertion started, but test has already finished'));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this.pendingAttemptCount > 0) {
|
|
339
|
+
this.saveFirstError(new Error('Assertion started, but an attempt is pending. Use the attempt’s assertions instead'));
|
|
196
340
|
}
|
|
197
341
|
|
|
198
342
|
this.assertCount++;
|
|
@@ -212,18 +356,60 @@ class Test {
|
|
|
212
356
|
this.saveFirstError(new Error('Assertion failed, but test has already finished'));
|
|
213
357
|
}
|
|
214
358
|
|
|
359
|
+
if (this.pendingAttemptCount > 0) {
|
|
360
|
+
this.saveFirstError(new Error('Assertion failed, but an attempt is pending. Use the attempt’s assertions instead'));
|
|
361
|
+
}
|
|
362
|
+
|
|
215
363
|
this.assertCount++;
|
|
216
364
|
this.refreshTimeout();
|
|
217
365
|
this.saveFirstError(error);
|
|
218
366
|
}
|
|
219
367
|
|
|
368
|
+
finishAttempt({commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) {
|
|
369
|
+
if (this.finishing) {
|
|
370
|
+
if (commit) {
|
|
371
|
+
this.saveFirstError(new Error('`t.try()` result was committed, but the test has already finished'));
|
|
372
|
+
} else {
|
|
373
|
+
this.saveFirstError(new Error('`t.try()` result was discarded, but the test has already finished'));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (commit) {
|
|
378
|
+
this.assertCount++;
|
|
379
|
+
|
|
380
|
+
if (startingSnapshotCount === this.snapshotCount) {
|
|
381
|
+
this.snapshotCount += snapshotCount;
|
|
382
|
+
this.nextSnapshotIndex += snapshotCount;
|
|
383
|
+
for (const record of deferredSnapshotRecordings) {
|
|
384
|
+
record();
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
this.saveFirstError(new Error('Cannot commit `t.try()` result. Do not run concurrent snapshot assertions when using `t.try()`'));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
this.pendingAttemptCount--;
|
|
392
|
+
|
|
393
|
+
if (commit && !passed) {
|
|
394
|
+
this.saveFirstError(errors[0]);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (retainLogs) {
|
|
398
|
+
for (const log of logs) {
|
|
399
|
+
this.addLog(log);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
this.refreshTimeout();
|
|
404
|
+
}
|
|
405
|
+
|
|
220
406
|
saveFirstError(error) {
|
|
221
407
|
if (!this.assertError) {
|
|
222
408
|
this.assertError = error;
|
|
223
409
|
}
|
|
224
410
|
}
|
|
225
411
|
|
|
226
|
-
plan(count,
|
|
412
|
+
plan(count, planError) {
|
|
227
413
|
if (typeof count !== 'number') {
|
|
228
414
|
throw new TypeError('Expected a number');
|
|
229
415
|
}
|
|
@@ -232,7 +418,7 @@ class Test {
|
|
|
232
418
|
|
|
233
419
|
// In case the `planCount` doesn't match `assertCount, we need the stack of
|
|
234
420
|
// this function to throw with a useful stack.
|
|
235
|
-
this.
|
|
421
|
+
this.planError = planError;
|
|
236
422
|
}
|
|
237
423
|
|
|
238
424
|
timeout(ms) {
|
|
@@ -274,17 +460,33 @@ class Test {
|
|
|
274
460
|
assertion: 'plan',
|
|
275
461
|
message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`,
|
|
276
462
|
operator: '===',
|
|
277
|
-
|
|
463
|
+
savedError: this.planError
|
|
278
464
|
}));
|
|
279
465
|
}
|
|
280
466
|
}
|
|
281
467
|
|
|
282
468
|
verifyAssertions() {
|
|
283
|
-
if (
|
|
284
|
-
|
|
469
|
+
if (this.assertError) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (this.pendingAttemptCount > 0) {
|
|
474
|
+
this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded'));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (this.pendingAssertionCount > 0) {
|
|
479
|
+
this.saveFirstError(new Error('Test finished, but an assertion is still pending'));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (this.failWithoutAssertions) {
|
|
484
|
+
if (this.planCount !== null) {
|
|
485
|
+
return; // `verifyPlan()` will report an error already.
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (this.assertCount === 0 && !this.calledEnd) {
|
|
285
489
|
this.saveFirstError(new Error('Test finished without running any assertions'));
|
|
286
|
-
} else if (this.pendingAssertionCount > 0) {
|
|
287
|
-
this.saveFirstError(new Error('Test finished, but an assertion is still pending'));
|
|
288
490
|
}
|
|
289
491
|
}
|
|
290
492
|
}
|
|
@@ -311,7 +513,7 @@ class Test {
|
|
|
311
513
|
fixedSource: {file: pending.file, line: pending.line},
|
|
312
514
|
improperUsage: true,
|
|
313
515
|
message: `Improper usage of \`t.${pending.assertion}()\` detected`,
|
|
314
|
-
|
|
516
|
+
savedError: error instanceof Error && error,
|
|
315
517
|
values
|
|
316
518
|
}));
|
|
317
519
|
return true;
|
|
@@ -365,7 +567,7 @@ class Test {
|
|
|
365
567
|
if (!this.detectImproperThrows(result.error)) {
|
|
366
568
|
this.saveFirstError(new assert.AssertionError({
|
|
367
569
|
message: 'Error thrown in test',
|
|
368
|
-
|
|
570
|
+
savedError: result.error instanceof Error && result.error,
|
|
369
571
|
values: [formatErrorValue('Error thrown in test:', result.error)]
|
|
370
572
|
}));
|
|
371
573
|
}
|
|
@@ -438,7 +640,7 @@ class Test {
|
|
|
438
640
|
if (!this.detectImproperThrows(error)) {
|
|
439
641
|
this.saveFirstError(new assert.AssertionError({
|
|
440
642
|
message: 'Rejected promise returned by test',
|
|
441
|
-
|
|
643
|
+
savedError: error instanceof Error && error,
|
|
442
644
|
values: [formatErrorValue('Rejected promise returned by test. Reason:', error)]
|
|
443
645
|
}));
|
|
444
646
|
}
|
|
@@ -477,11 +679,14 @@ class Test {
|
|
|
477
679
|
}
|
|
478
680
|
|
|
479
681
|
return {
|
|
682
|
+
deferredSnapshotRecordings: this.deferredSnapshotRecordings,
|
|
480
683
|
duration: this.duration,
|
|
481
684
|
error,
|
|
482
685
|
logs: this.logs,
|
|
483
686
|
metadata: this.metadata,
|
|
484
687
|
passed,
|
|
688
|
+
snapshotCount: this.snapshotCount,
|
|
689
|
+
assertCount: this.assertCount,
|
|
485
690
|
title: this.title
|
|
486
691
|
};
|
|
487
692
|
}
|
package/lib/watcher.js
CHANGED
|
@@ -162,7 +162,7 @@ class Watcher {
|
|
|
162
162
|
}).on('all', (event, path) => {
|
|
163
163
|
if (event === 'add' || event === 'change' || event === 'unlink') {
|
|
164
164
|
debug('Detected %s of %s', event, path);
|
|
165
|
-
this.dirtyStates[path] = event;
|
|
165
|
+
this.dirtyStates[nodePath.join(this.globs.cwd, path)] = event;
|
|
166
166
|
this.debouncer.debounce();
|
|
167
167
|
}
|
|
168
168
|
});
|
|
@@ -180,7 +180,7 @@ class Watcher {
|
|
|
180
180
|
const dependencies = evt.dependencies.map(x => relative(x)).filter(filePath => {
|
|
181
181
|
const {isHelper, isSource} = globs.classify(filePath, this.globs);
|
|
182
182
|
return isHelper || isSource;
|
|
183
|
-
});
|
|
183
|
+
}).map(x => nodePath.join(this.globs.cwd, x));
|
|
184
184
|
this.updateTestDependencies(evt.testFile, dependencies);
|
|
185
185
|
});
|
|
186
186
|
});
|
|
@@ -215,7 +215,7 @@ class Watcher {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
for (const file of evt.files) {
|
|
218
|
-
this.touchedFiles.add(
|
|
218
|
+
this.touchedFiles.add(file);
|
|
219
219
|
}
|
|
220
220
|
});
|
|
221
221
|
});
|
|
@@ -229,7 +229,8 @@ class Watcher {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
const fileStats = plan.status.stats.byFile.get(evt.testFile);
|
|
232
|
-
|
|
232
|
+
const ranExclusiveTests = fileStats.selectedTests > 0 && fileStats.declaredTests > fileStats.selectedTests;
|
|
233
|
+
this.updateExclusivity(evt.testFile, ranExclusiveTests);
|
|
233
234
|
});
|
|
234
235
|
});
|
|
235
236
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const tty = require('tty');
|
|
3
|
+
|
|
4
|
+
// Call original method to ensure the correct errors are thrown.
|
|
5
|
+
const assertHasColorsArguments = count => {
|
|
6
|
+
tty.WriteStream.prototype.hasColors(count);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const makeHasColors = colorDepth => (count = 16, env) => {
|
|
10
|
+
// `count` is optional too, so make sure it's not an env object.
|
|
11
|
+
if (env === undefined && typeof count === 'object' && count !== null) {
|
|
12
|
+
count = 16;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
assertHasColorsArguments(count);
|
|
16
|
+
return count <= 2 ** colorDepth;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = makeHasColors;
|
package/lib/worker/fake-tty.js
CHANGED
|
@@ -2,39 +2,85 @@
|
|
|
2
2
|
const tty = require('tty');
|
|
3
3
|
const ansiEscapes = require('ansi-escapes');
|
|
4
4
|
const options = require('./options').get();
|
|
5
|
+
const makeHasColors = require('./fake-tty-has-colors');
|
|
5
6
|
|
|
6
7
|
const fakeTTYs = new Set();
|
|
7
8
|
|
|
8
9
|
const {isatty} = tty;
|
|
9
10
|
tty.isatty = fd => fakeTTYs.has(fd) || isatty(fd);
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
const takesCallbackAndReturnWriteResult = tty.WriteStream.prototype.clearLine.length === 1;
|
|
13
|
+
|
|
14
|
+
const assertCallback = callback => {
|
|
15
|
+
// FIXME: Better replicate Node.js' internal errors.
|
|
16
|
+
if (callback !== undefined && typeof callback !== 'function') {
|
|
17
|
+
const error = new TypeError('Callback must be a function');
|
|
18
|
+
error.code = 'ERR_INVALID_CALLBACK';
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const fakeWriters = {
|
|
24
|
+
clearLine(dir, callback) {
|
|
25
|
+
assertCallback(callback);
|
|
13
26
|
|
|
14
|
-
stream.clearLine = dir => {
|
|
15
27
|
switch (dir) {
|
|
16
28
|
case -1:
|
|
17
|
-
|
|
18
|
-
break;
|
|
29
|
+
return this.write(ansiEscapes.eraseStartLine, callback);
|
|
19
30
|
case 1:
|
|
20
|
-
|
|
21
|
-
break;
|
|
31
|
+
return this.write(ansiEscapes.eraseEndLine, callback);
|
|
22
32
|
default:
|
|
23
|
-
|
|
33
|
+
return this.write(ansiEscapes.eraseLine, callback);
|
|
24
34
|
}
|
|
25
|
-
}
|
|
35
|
+
},
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
clearScreenDown(callback) {
|
|
38
|
+
assertCallback(callback);
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
return this.write(ansiEscapes.eraseDown, callback);
|
|
41
|
+
},
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
cursorTo(x, y, callback) {
|
|
44
|
+
assertCallback(callback);
|
|
45
|
+
return this.write(ansiEscapes.cursorTo(x, y), callback);
|
|
46
|
+
},
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
moveCursor(x, y, callback) {
|
|
49
|
+
assertCallback(callback);
|
|
50
|
+
return this.write(ansiEscapes.cursorMove(x, y), callback);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
34
53
|
|
|
54
|
+
const simulateTTY = (stream, {colorDepth, hasColors, columns, rows}) => {
|
|
55
|
+
Object.assign(stream, {isTTY: true, columns, rows});
|
|
56
|
+
|
|
57
|
+
if (takesCallbackAndReturnWriteResult) {
|
|
58
|
+
Object.assign(stream, fakeWriters);
|
|
59
|
+
} else {
|
|
60
|
+
Object.assign(stream, {
|
|
61
|
+
clearLine(dir) {
|
|
62
|
+
fakeWriters.clearLine.call(this, dir);
|
|
63
|
+
},
|
|
64
|
+
clearScreenDown() {
|
|
65
|
+
fakeWriters.clearScreenDown.call(this);
|
|
66
|
+
},
|
|
67
|
+
cursorTo(x, y) {
|
|
68
|
+
fakeWriters.cursorTo.call(this, x, y);
|
|
69
|
+
},
|
|
70
|
+
moveCursor(x, y) {
|
|
71
|
+
fakeWriters.moveCursor.call(this, x, y);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
stream.getWindowSize = () => [80, 24];
|
|
35
77
|
if (colorDepth !== undefined) {
|
|
36
78
|
stream.getColorDepth = () => colorDepth;
|
|
37
79
|
}
|
|
80
|
+
|
|
81
|
+
if (hasColors) {
|
|
82
|
+
stream.hasColors = makeHasColors(colorDepth);
|
|
83
|
+
}
|
|
38
84
|
};
|
|
39
85
|
|
|
40
86
|
if (options.tty.stderr) {
|