watcher.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577
  1. /* eslint-disable promise/no-callback-in-promise */
  2. 'use strict';
  3. const path = require('path');
  4. const EventEmitter = require('events');
  5. const PassThrough = require('stream').PassThrough;
  6. const defaultIgnore = require('ignore-by-default').directories();
  7. const lolex = require('lolex');
  8. const proxyquire = require('proxyquire');
  9. const sinon = require('sinon');
  10. const test = require('tap').test;
  11. const AvaFiles = require('../lib/ava-files');
  12. const setImmediate = require('../lib/now-and-timers').setImmediate;
  13. // Helper to make using beforeEach less arduous
  14. function makeGroup(test) {
  15. return (desc, fn) => {
  16. test(desc, t => {
  17. const beforeEach = fn => {
  18. t.beforeEach(done => {
  19. fn();
  20. done();
  21. });
  22. };
  23. const pending = [];
  24. const test = (name, fn) => {
  25. pending.push(t.test(name, fn));
  26. };
  27. fn(beforeEach, test, makeGroup(test));
  28. return Promise.all(pending);
  29. });
  30. };
  31. }
  32. const group = makeGroup(test);
  33. group('chokidar', (beforeEach, test, group) => {
  34. let chokidar;
  35. let debug;
  36. let reporter;
  37. let api;
  38. let avaFiles;
  39. let Subject;
  40. let runStatus;
  41. let resetRunStatus;
  42. let clock;
  43. let chokidarEmitter;
  44. let stdin;
  45. let files;
  46. let defaultApiOptions;
  47. function proxyWatcher(opts) {
  48. return proxyquire.noCallThru().load('../lib/watcher', opts ||
  49. {
  50. chokidar,
  51. debug(name) {
  52. return function () {
  53. const args = [name];
  54. args.push.apply(args, arguments);
  55. debug.apply(null, args);
  56. };
  57. },
  58. './ava-files': avaFiles
  59. });
  60. }
  61. beforeEach(() => {
  62. chokidar = {
  63. watch: sinon.stub()
  64. };
  65. debug = sinon.spy();
  66. reporter = {
  67. endRun: sinon.spy()
  68. };
  69. api = {
  70. on() {},
  71. run: sinon.stub()
  72. };
  73. resetRunStatus = () => {
  74. runStatus = {
  75. stats: {
  76. byFile: new Map(),
  77. declaredTests: 0,
  78. failedHooks: 0,
  79. failedTests: 0,
  80. failedWorkers: 0,
  81. files,
  82. finishedWorkers: 0,
  83. internalErrors: 0,
  84. remainingTests: 0,
  85. passedKnownFailingTests: 0,
  86. passedTests: 0,
  87. selectedTests: 0,
  88. skippedTests: 0,
  89. timeouts: 0,
  90. todoTests: 0,
  91. uncaughtExceptions: 0,
  92. unhandledRejections: 0
  93. }
  94. };
  95. return runStatus;
  96. };
  97. if (clock) {
  98. clock.uninstall();
  99. }
  100. clock = lolex.install({
  101. toFake: [
  102. 'setImmediate',
  103. 'setTimeout',
  104. 'clearTimeout'
  105. ]
  106. });
  107. chokidarEmitter = new EventEmitter();
  108. chokidar.watch.returns(chokidarEmitter);
  109. avaFiles = AvaFiles;
  110. api.run.returns(new Promise(() => {}));
  111. files = [
  112. 'test.js',
  113. 'test-*.js',
  114. 'test'
  115. ];
  116. defaultApiOptions = {
  117. clearLogOnNextRun: false,
  118. previousFailures: 0,
  119. runOnlyExclusive: false,
  120. runVector: 1,
  121. updateSnapshots: false
  122. };
  123. resetRunStatus();
  124. stdin = new PassThrough();
  125. stdin.pause();
  126. Subject = proxyWatcher();
  127. });
  128. const start = (specificFiles, sources) => new Subject(reporter, api, specificFiles || files, sources || []);
  129. const emitChokidar = (event, path) => {
  130. chokidarEmitter.emit('all', event, path);
  131. };
  132. const add = path => {
  133. emitChokidar('add', path || 'source.js');
  134. };
  135. const change = path => {
  136. emitChokidar('change', path || 'source.js');
  137. };
  138. const unlink = path => {
  139. emitChokidar('unlink', path || 'source.js');
  140. };
  141. const delay = () => new Promise(resolve => {
  142. setImmediate(resolve);
  143. });
  144. // Advance the clock to get past the debounce timeout, then wait for a promise
  145. // to be resolved to get past the `busy.then()` delay
  146. const debounce = times => {
  147. times = times >= 0 ? times : 1;
  148. clock.next();
  149. return delay().then(() => {
  150. if (times > 1) {
  151. return debounce(times - 1);
  152. }
  153. });
  154. };
  155. test('watches for default source file changes, as well as test files', t => {
  156. t.plan(2);
  157. start();
  158. t.ok(chokidar.watch.calledOnce);
  159. t.strictDeepEqual(chokidar.watch.firstCall.args, [
  160. ['package.json', '**/*.js', '**/*.snap'].concat(files),
  161. {
  162. ignored: defaultIgnore.map(dir => `${dir}/**/*`),
  163. ignoreInitial: true
  164. }
  165. ]);
  166. });
  167. test('watched source files are configurable', t => {
  168. t.plan(2);
  169. start(null, ['foo.js', '!bar.js', 'baz.js', '!qux.js']);
  170. t.ok(chokidar.watch.calledOnce);
  171. t.strictDeepEqual(chokidar.watch.firstCall.args, [
  172. ['foo.js', 'baz.js'].concat(files),
  173. {
  174. ignored: defaultIgnore.map(dir => `${dir}/**/*`).concat('bar.js', 'qux.js'),
  175. ignoreInitial: true
  176. }
  177. ]);
  178. });
  179. test('configured sources can override default ignore patterns', t => {
  180. t.plan(2);
  181. start(null, ['node_modules/foo/*.js']);
  182. t.ok(chokidar.watch.calledOnce);
  183. t.strictDeepEqual(chokidar.watch.firstCall.args, [
  184. ['node_modules/foo/*.js'].concat(files),
  185. {
  186. ignored: defaultIgnore.map(dir => `${dir}/**/*`).concat('!node_modules/foo/*.js'),
  187. ignoreInitial: true
  188. }
  189. ]);
  190. });
  191. test('starts running the initial tests', t => {
  192. t.plan(4);
  193. let done;
  194. api.run.returns(new Promise(resolve => {
  195. done = () => {
  196. resolve(runStatus);
  197. };
  198. }));
  199. start();
  200. t.ok(api.run.calledOnce);
  201. t.strictDeepEqual(api.run.firstCall.args, [files, defaultApiOptions]);
  202. // The endRun method is only called after the run promise fulfils
  203. t.ok(reporter.endRun.notCalled);
  204. done();
  205. return delay().then(() => {
  206. t.ok(reporter.endRun.calledOnce);
  207. });
  208. });
  209. [
  210. {
  211. label: 'is added',
  212. fire: add,
  213. event: 'add'
  214. },
  215. {
  216. label: 'changes',
  217. fire: change,
  218. event: 'change'
  219. },
  220. {
  221. label: 'is removed',
  222. fire: unlink,
  223. event: 'unlink'
  224. }
  225. ].forEach(variant => {
  226. test(`logs a debug message when a file is ${variant.label}`, t => {
  227. t.plan(2);
  228. start();
  229. variant.fire('file.js');
  230. t.ok(debug.calledOnce);
  231. t.strictDeepEqual(debug.firstCall.args, ['ava:watcher', 'Detected %s of %s', variant.event, 'file.js']);
  232. });
  233. });
  234. [
  235. {
  236. label: 'is added',
  237. fire: add
  238. },
  239. {
  240. label: 'changes',
  241. fire: change
  242. },
  243. {
  244. label: 'is removed',
  245. fire: unlink
  246. }
  247. ].forEach(variant => {
  248. test(`reruns initial tests when a source file ${variant.label}`, t => {
  249. t.plan(4);
  250. api.run.returns(Promise.resolve(runStatus));
  251. start();
  252. let done;
  253. api.run.returns(new Promise(resolve => {
  254. done = () => {
  255. resolve(runStatus);
  256. };
  257. }));
  258. variant.fire();
  259. return debounce().then(() => {
  260. t.ok(api.run.calledTwice);
  261. // No explicit files are provided
  262. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  263. clearLogOnNextRun: true,
  264. runVector: 2
  265. })]);
  266. // Finish is only called after the run promise fulfils
  267. t.ok(reporter.endRun.calledOnce);
  268. resetRunStatus();
  269. done();
  270. return delay();
  271. }).then(() => {
  272. t.ok(reporter.endRun.calledTwice);
  273. });
  274. });
  275. });
  276. [
  277. {
  278. label: 'failures',
  279. prop: 'failedTests'
  280. },
  281. {
  282. label: 'rejections',
  283. prop: 'unhandledRejections'
  284. },
  285. {
  286. label: 'exceptions',
  287. prop: 'uncaughtExceptions'
  288. }
  289. ].forEach(variant => {
  290. test(`does not clear log if the previous run had ${variant.label}`, t => {
  291. t.plan(2);
  292. runStatus.stats[variant.prop] = 1;
  293. api.run.returns(Promise.resolve(runStatus));
  294. start();
  295. api.run.returns(Promise.resolve(resetRunStatus()));
  296. change();
  297. return debounce().then(() => {
  298. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  299. clearLogOnNextRun: false,
  300. runVector: 2
  301. })]);
  302. change();
  303. return debounce();
  304. }).then(() => {
  305. t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, {
  306. clearLogOnNextRun: true,
  307. runVector: 3
  308. })]);
  309. });
  310. });
  311. });
  312. test('debounces by 100ms', t => {
  313. t.plan(1);
  314. api.run.returns(Promise.resolve(runStatus));
  315. start();
  316. change();
  317. const before = clock.now;
  318. return debounce().then(() => {
  319. t.is(clock.now - before, 100);
  320. });
  321. });
  322. test('debounces again if changes occur in the interval', t => {
  323. t.plan(4);
  324. api.run.returns(Promise.resolve(runStatus));
  325. start();
  326. change();
  327. change();
  328. const before = clock.now;
  329. return debounce().then(() => {
  330. change();
  331. return debounce();
  332. }).then(() => {
  333. t.is(clock.now - before, 150);
  334. change();
  335. return debounce();
  336. }).then(() => {
  337. t.is(clock.now - before, 175);
  338. change();
  339. return debounce();
  340. }).then(() => {
  341. t.is(clock.now - before, 187);
  342. change();
  343. return debounce();
  344. }).then(() => {
  345. t.is(clock.now - before, 197);
  346. });
  347. });
  348. test('only reruns tests once the initial run has finished', t => {
  349. t.plan(2);
  350. let done;
  351. api.run.returns(new Promise(resolve => {
  352. done = () => {
  353. resolve(runStatus);
  354. };
  355. }));
  356. start();
  357. change();
  358. clock.next();
  359. return delay().then(() => {
  360. t.ok(api.run.calledOnce);
  361. done();
  362. return delay();
  363. }).then(() => {
  364. t.ok(api.run.calledTwice);
  365. });
  366. });
  367. test('only reruns tests once the previous run has finished', t => {
  368. t.plan(3);
  369. api.run.returns(Promise.resolve(runStatus));
  370. start();
  371. let done;
  372. api.run.returns(new Promise(resolve => {
  373. done = () => {
  374. resolve(runStatus);
  375. };
  376. }));
  377. change();
  378. return debounce().then(() => {
  379. t.ok(api.run.calledTwice);
  380. change();
  381. clock.next();
  382. return delay();
  383. }).then(() => {
  384. t.ok(api.run.calledTwice);
  385. done();
  386. return delay();
  387. }).then(() => {
  388. t.ok(api.run.calledThrice);
  389. });
  390. });
  391. [
  392. {
  393. label: 'is added',
  394. fire: add
  395. },
  396. {
  397. label: 'changes',
  398. fire: change
  399. }
  400. ].forEach(variant => {
  401. test(`(re)runs a test file when it ${variant.label}`, t => {
  402. t.plan(4);
  403. api.run.returns(Promise.resolve(runStatus));
  404. start();
  405. let done;
  406. api.run.returns(new Promise(resolve => {
  407. done = () => {
  408. resolve(runStatus);
  409. };
  410. }));
  411. variant.fire('test.js');
  412. return debounce().then(() => {
  413. t.ok(api.run.calledTwice);
  414. // The `test.js` file is provided
  415. t.strictDeepEqual(api.run.secondCall.args, [['test.js'], Object.assign({}, defaultApiOptions, {
  416. clearLogOnNextRun: true,
  417. runVector: 2
  418. })]);
  419. // The endRun method is only called after the run promise fulfills
  420. t.ok(reporter.endRun.calledOnce);
  421. resetRunStatus();
  422. done();
  423. return delay();
  424. }).then(() => {
  425. t.ok(reporter.endRun.calledTwice);
  426. });
  427. });
  428. });
  429. test('(re)runs several test files when they are added or changed', t => {
  430. t.plan(2);
  431. api.run.returns(Promise.resolve(runStatus));
  432. start();
  433. add('test-one.js');
  434. change('test-two.js');
  435. return debounce(2).then(() => {
  436. t.ok(api.run.calledTwice);
  437. // The test files are provided
  438. t.strictDeepEqual(api.run.secondCall.args, [['test-one.js', 'test-two.js'], Object.assign({}, defaultApiOptions, {
  439. clearLogOnNextRun: true,
  440. runVector: 2
  441. })]);
  442. });
  443. });
  444. test('reruns initial tests if both source and test files are added or changed', t => {
  445. t.plan(2);
  446. api.run.returns(Promise.resolve(runStatus));
  447. start();
  448. add('test.js');
  449. unlink('source.js');
  450. return debounce(2).then(() => {
  451. t.ok(api.run.calledTwice);
  452. // No explicit files are provided
  453. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  454. clearLogOnNextRun: true,
  455. runVector: 2
  456. })]);
  457. });
  458. });
  459. test('does nothing if tests are deleted', t => {
  460. t.plan(1);
  461. api.run.returns(Promise.resolve(runStatus));
  462. start();
  463. unlink('test.js');
  464. return debounce().then(() => {
  465. t.ok(api.run.calledOnce);
  466. });
  467. });
  468. test('determines whether changed files are tests based on the initial files patterns', t => {
  469. t.plan(2);
  470. files = ['foo-{bar,baz}.js'];
  471. api.run.returns(Promise.resolve(runStatus));
  472. start();
  473. add('foo-bar.js');
  474. add('foo-baz.js');
  475. return debounce(2).then(() => {
  476. t.ok(api.run.calledTwice);
  477. t.strictDeepEqual(api.run.secondCall.args, [['foo-bar.js', 'foo-baz.js'], Object.assign({}, defaultApiOptions, {
  478. clearLogOnNextRun: true,
  479. runVector: 2
  480. })]);
  481. });
  482. });
  483. test('initial exclude patterns override whether something is a test file', t => {
  484. t.plan(2);
  485. avaFiles = function (options) {
  486. const ret = new AvaFiles(options);
  487. // Note: There is no way for users to actually set exclude patterns yet.
  488. // This test just validates that internal updates to the default excludes pattern will be obeyed.
  489. ret.excludePatterns = ['!*bar*'];
  490. return ret;
  491. };
  492. Subject = proxyWatcher();
  493. files = ['foo-{bar,baz}.js'];
  494. api.run.returns(Promise.resolve(runStatus));
  495. start();
  496. add('foo-bar.js');
  497. add('foo-baz.js');
  498. return debounce(2).then(() => {
  499. t.ok(api.run.calledTwice);
  500. // `foo-bar.js` is excluded from being a test file, thus the initial tests
  501. // are run
  502. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  503. clearLogOnNextRun: true,
  504. runVector: 2
  505. })]);
  506. });
  507. });
  508. test('test files must end in .js', t => {
  509. t.plan(2);
  510. files = ['foo.bar'];
  511. api.run.returns(Promise.resolve(runStatus));
  512. start();
  513. add('foo.bar');
  514. return debounce(2).then(() => {
  515. t.ok(api.run.calledTwice);
  516. // `foo.bar` cannot be a test file, thus the initial tests are run
  517. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  518. clearLogOnNextRun: true,
  519. runVector: 2
  520. })]);
  521. });
  522. });
  523. test('test files must not start with an underscore', t => {
  524. t.plan(2);
  525. api.files = ['_foo.bar'];
  526. api.run.returns(Promise.resolve(runStatus));
  527. start();
  528. add('_foo.bar');
  529. return debounce(2).then(() => {
  530. t.ok(api.run.calledTwice);
  531. // `_foo.bar` cannot be a test file, thus the initial tests are run
  532. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  533. clearLogOnNextRun: true,
  534. runVector: 2
  535. })]);
  536. });
  537. });
  538. test('files patterns may match directories', t => {
  539. t.plan(2);
  540. files = ['dir', 'another-dir/*/deeper'];
  541. api.run.returns(Promise.resolve(runStatus));
  542. start();
  543. add(path.join('dir', 'test.js'));
  544. add(path.join('dir', 'nested', 'test.js'));
  545. add(path.join('another-dir', 'nested', 'deeper', 'test.js'));
  546. return debounce(3).then(() => {
  547. t.ok(api.run.calledTwice);
  548. t.strictDeepEqual(api.run.secondCall.args, [
  549. [
  550. path.join('dir', 'test.js'),
  551. path.join('dir', 'nested', 'test.js'),
  552. path.join('another-dir', 'nested', 'deeper', 'test.js')
  553. ],
  554. Object.assign({}, defaultApiOptions, {
  555. clearLogOnNextRun: true,
  556. runVector: 2
  557. })
  558. ]);
  559. });
  560. });
  561. test('exclude patterns override directory matches', t => {
  562. t.plan(2);
  563. avaFiles = function (options) {
  564. const ret = new AvaFiles(options);
  565. // Note: There is no way for users to actually set exclude patterns yet.
  566. // This test just validates that internal updates to the default excludes pattern will be obeyed.
  567. ret.excludePatterns = ['!**/exclude/**'];
  568. return ret;
  569. };
  570. Subject = proxyWatcher();
  571. files = ['dir'];
  572. api.run.returns(Promise.resolve(runStatus));
  573. start();
  574. add(path.join('dir', 'exclude', 'foo.js'));
  575. return debounce(2).then(() => {
  576. t.ok(api.run.calledTwice);
  577. // `dir/exclude/foo.js` is excluded from being a test file, thus the initial
  578. // tests are run
  579. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  580. clearLogOnNextRun: true,
  581. runVector: 2
  582. })]);
  583. });
  584. });
  585. ['r', 'rs'].forEach(input => {
  586. test(`reruns initial tests when "${input}" is entered on stdin`, t => {
  587. t.plan(4);
  588. api.run.returns(Promise.resolve(runStatus));
  589. start().observeStdin(stdin);
  590. stdin.write(`${input}\n`);
  591. return delay().then(() => {
  592. t.ok(api.run.calledTwice);
  593. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  594. runVector: 2
  595. })]);
  596. stdin.write(`\t${input} \n`);
  597. return delay();
  598. }).then(() => {
  599. t.ok(api.run.calledThrice);
  600. t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, {
  601. runVector: 3
  602. })]);
  603. });
  604. });
  605. });
  606. test(`reruns previous tests and update snapshots when "u" is entered on stdin`, t => {
  607. const options = Object.assign({}, defaultApiOptions, {updateSnapshots: true});
  608. const previousFiles = ['test.js'];
  609. t.plan(4);
  610. api.run.returns(Promise.resolve(runStatus));
  611. start(previousFiles).observeStdin(stdin);
  612. stdin.write(`u\n`);
  613. return delay().then(() => {
  614. t.ok(api.run.calledTwice);
  615. t.strictDeepEqual(api.run.secondCall.args, [previousFiles, Object.assign({}, options, {
  616. runVector: 2
  617. })]);
  618. stdin.write(`\tu \n`);
  619. return delay();
  620. }).then(() => {
  621. t.ok(api.run.calledThrice);
  622. t.strictDeepEqual(api.run.thirdCall.args, [previousFiles, Object.assign({}, options, {
  623. runVector: 3
  624. })]);
  625. });
  626. });
  627. ['r', 'rs', 'u'].forEach(input => {
  628. test(`entering "${input}" on stdin prevents the log from being cleared`, t => {
  629. t.plan(2);
  630. api.run.returns(Promise.resolve(runStatus));
  631. start().observeStdin(stdin);
  632. stdin.write(`${input}\n`);
  633. return delay().then(() => {
  634. t.ok(api.run.calledTwice);
  635. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  636. clearLogOnNextRun: false,
  637. runVector: 2,
  638. updateSnapshots: input === 'u'
  639. })]);
  640. });
  641. });
  642. test(`entering "${input}" on stdin cancels any debouncing`, t => {
  643. t.plan(7);
  644. api.run.returns(Promise.resolve(runStatus));
  645. start().observeStdin(stdin);
  646. let before = clock.now;
  647. let done;
  648. api.run.returns(new Promise(resolve => {
  649. done = () => {
  650. resolve(runStatus);
  651. };
  652. }));
  653. add();
  654. stdin.write(`${input}\n`);
  655. return delay().then(() => {
  656. // Processing "rs" caused a new run
  657. t.ok(api.run.calledTwice);
  658. // Try to advance the clock. This is *after* input was processed. The
  659. // debounce timeout should have been canceled, so the clock can't have
  660. // advanced.
  661. clock.next();
  662. t.is(before, clock.now);
  663. add();
  664. // Advance clock *before* input is received. Note that the previous run
  665. // hasn't finished yet.
  666. clock.next();
  667. stdin.write(`${input}\n`);
  668. return delay();
  669. }).then(() => {
  670. // No new runs yet
  671. t.ok(api.run.calledTwice);
  672. // Though the clock has advanced
  673. t.is(clock.now - before, 100);
  674. before = clock.now;
  675. const previous = done;
  676. api.run.returns(new Promise(resolve => {
  677. done = () => {
  678. resolve(runStatus);
  679. };
  680. }));
  681. // Finish the previous run
  682. previous();
  683. return delay();
  684. }).then(() => {
  685. // There's only one new run
  686. t.ok(api.run.calledThrice);
  687. stdin.write(`${input}\n`);
  688. return delay();
  689. }).then(() => {
  690. add();
  691. // Finish the previous run. This should cause a new run due to the
  692. // input.
  693. done();
  694. return delay();
  695. }).then(() => {
  696. // Again there's only one new run
  697. t.is(api.run.callCount, 4);
  698. // Try to advance the clock. This is *after* input was processed. The
  699. // debounce timeout should have been canceled, so the clock can't have
  700. // advanced.
  701. clock.next();
  702. t.is(before, clock.now);
  703. });
  704. });
  705. });
  706. test('does nothing if anything other than "rs" is entered on stdin', t => {
  707. t.plan(1);
  708. api.run.returns(Promise.resolve(runStatus));
  709. start().observeStdin(stdin);
  710. stdin.write('foo\n');
  711. return debounce().then(() => {
  712. t.ok(api.run.calledOnce);
  713. });
  714. });
  715. test('ignores unexpected events from chokidar', t => {
  716. t.plan(1);
  717. api.run.returns(Promise.resolve(runStatus));
  718. start();
  719. emitChokidar('foo');
  720. return debounce().then(() => {
  721. t.ok(api.run.calledOnce);
  722. });
  723. });
  724. test('initial run rejects', t => {
  725. t.plan(1);
  726. const expected = new Error();
  727. api.run.returns(Promise.reject(expected));
  728. start();
  729. return delay().then(() => {
  730. // The error is rethrown asynchronously, using setImmediate. The clock has
  731. // faked setTimeout, so if we call clock.next() it'll invoke and rethrow
  732. // the error, which can then be caught here.
  733. try {
  734. clock.next();
  735. } catch (err) {
  736. t.is(err, expected);
  737. }
  738. });
  739. });
  740. test('subsequent run rejects', t => {
  741. t.plan(1);
  742. api.run.returns(Promise.resolve(runStatus));
  743. start();
  744. const expected = new Error();
  745. api.run.returns(Promise.reject(expected));
  746. add();
  747. return debounce().then(() => {
  748. // The error is rethrown asynchronously, using setImmediate. The clock has
  749. // faked setTimeout, so if we call clock.next() it'll invoke and rethrow
  750. // the error, which can then be caught here.
  751. try {
  752. clock.next();
  753. } catch (err) {
  754. t.is(err, expected);
  755. }
  756. });
  757. });
  758. group('tracks test dependencies', (beforeEach, test) => {
  759. let apiEmitter;
  760. let runStatus;
  761. let runStatusEmitter;
  762. beforeEach(() => {
  763. apiEmitter = new EventEmitter();
  764. api.on = (event, fn) => {
  765. apiEmitter.on(event, fn);
  766. };
  767. runStatusEmitter = new EventEmitter();
  768. runStatus = {
  769. stats: {
  770. byFile: new Map(),
  771. declaredTests: 0,
  772. failedHooks: 0,
  773. failedTests: 0,
  774. failedWorkers: 0,
  775. files,
  776. finishedWorkers: 0,
  777. internalErrors: 0,
  778. remainingTests: 0,
  779. passedKnownFailingTests: 0,
  780. passedTests: 0,
  781. selectedTests: 0,
  782. skippedTests: 0,
  783. timeouts: 0,
  784. todoTests: 0,
  785. uncaughtExceptions: 0,
  786. unhandledRejections: 0
  787. },
  788. on(event, fn) {
  789. runStatusEmitter.on(event, fn);
  790. }
  791. };
  792. });
  793. const emitDependencies = (testFile, dependencies) => {
  794. runStatusEmitter.emit('stateChange', {type: 'dependencies', testFile, dependencies});
  795. };
  796. const seed = sources => {
  797. let done;
  798. api.run.returns(new Promise(resolve => {
  799. done = () => {
  800. resolve(runStatus);
  801. };
  802. }));
  803. const watcher = start(null, sources);
  804. const files = [path.join('test', '1.js'), path.join('test', '2.js')];
  805. const absFiles = files.map(relFile => path.resolve(relFile));
  806. apiEmitter.emit('run', {
  807. files: absFiles,
  808. status: runStatus
  809. });
  810. emitDependencies(files[0], [path.resolve('dep-1.js'), path.resolve('dep-3.js')]);
  811. emitDependencies(files[1], [path.resolve('dep-2.js'), path.resolve('dep-3.js')]);
  812. done();
  813. api.run.returns(new Promise(() => {}));
  814. return watcher;
  815. };
  816. test('runs specific tests that depend on changed sources', t => {
  817. t.plan(2);
  818. seed();
  819. change('dep-1.js');
  820. return debounce().then(() => {
  821. t.ok(api.run.calledTwice);
  822. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, {
  823. clearLogOnNextRun: true,
  824. runVector: 2
  825. })]);
  826. });
  827. });
  828. test('reruns all tests if a source cannot be mapped to a particular test', t => {
  829. t.plan(2);
  830. seed();
  831. change('cannot-be-mapped.js');
  832. return debounce().then(() => {
  833. t.ok(api.run.calledTwice);
  834. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  835. clearLogOnNextRun: true,
  836. runVector: 2
  837. })]);
  838. });
  839. });
  840. test('runs changed tests and tests that depend on changed sources', t => {
  841. t.plan(2);
  842. seed();
  843. change('dep-1.js');
  844. change(path.join('test', '2.js'));
  845. return debounce(2).then(() => {
  846. t.ok(api.run.calledTwice);
  847. t.strictDeepEqual(api.run.secondCall.args, [
  848. [path.join('test', '2.js'), path.join('test', '1.js')],
  849. Object.assign({}, defaultApiOptions, {
  850. clearLogOnNextRun: true,
  851. runVector: 2
  852. })
  853. ]);
  854. });
  855. });
  856. test('avoids duplication when both a test and a source dependency change', t => {
  857. t.plan(2);
  858. seed();
  859. change(path.join('test', '1.js'));
  860. change('dep-1.js');
  861. return debounce(2).then(() => {
  862. t.ok(api.run.calledTwice);
  863. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, {
  864. clearLogOnNextRun: true,
  865. runVector: 2
  866. })]);
  867. });
  868. });
  869. test('stops tracking unlinked tests', t => {
  870. t.plan(2);
  871. seed();
  872. unlink(path.join('test', '1.js'));
  873. change('dep-3.js');
  874. return debounce(2).then(() => {
  875. t.ok(api.run.calledTwice);
  876. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '2.js')], Object.assign({}, defaultApiOptions, {
  877. clearLogOnNextRun: true,
  878. runVector: 2
  879. })]);
  880. });
  881. });
  882. test('updates test dependencies', t => {
  883. t.plan(2);
  884. seed();
  885. emitDependencies(path.join('test', '1.js'), [path.resolve('dep-4.js')]);
  886. change('dep-4.js');
  887. return debounce().then(() => {
  888. t.ok(api.run.calledTwice);
  889. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, {
  890. clearLogOnNextRun: true,
  891. runVector: 2
  892. })]);
  893. });
  894. });
  895. [
  896. {
  897. desc: 'only tracks source dependencies',
  898. sources: ['dep-1.js']
  899. },
  900. {
  901. desc: 'exclusion patterns affect tracked source dependencies',
  902. sources: ['!dep-2.js']
  903. }
  904. ].forEach(variant => {
  905. test(variant.desc, t => {
  906. t.plan(2);
  907. seed(variant.sources);
  908. // `dep-2.js` isn't treated as a source and therefore it's not tracked as
  909. // a dependency for `test/2.js`. Pretend Chokidar detected a change to
  910. // verify (normally Chokidar would also be ignoring this file but hey).
  911. change('dep-2.js');
  912. return debounce().then(() => {
  913. t.ok(api.run.calledTwice);
  914. // Expect all tests to be rerun since `dep-2.js` is not a tracked
  915. // dependency
  916. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  917. clearLogOnNextRun: true,
  918. runVector: 2
  919. })]);
  920. });
  921. });
  922. });
  923. test('uses default source patterns', t => {
  924. t.plan(4);
  925. seed();
  926. emitDependencies(path.join('test', '1.js'), [path.resolve('package.json'), path.resolve('index.js'), path.resolve('lib/util.js')]);
  927. emitDependencies(path.join('test', '2.js'), [path.resolve('foo.bar')]);
  928. change('package.json');
  929. change('index.js');
  930. change(path.join('lib', 'util.js'));
  931. api.run.returns(Promise.resolve(runStatus));
  932. return debounce(3).then(() => {
  933. t.ok(api.run.calledTwice);
  934. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, {
  935. clearLogOnNextRun: true,
  936. runVector: 2
  937. })]);
  938. change('foo.bar');
  939. return debounce();
  940. }).then(() => {
  941. t.ok(api.run.calledThrice);
  942. // Expect all tests to be rerun since `foo.bar` is not a tracked
  943. // dependency
  944. t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, {
  945. clearLogOnNextRun: true,
  946. runVector: 3
  947. })]);
  948. });
  949. });
  950. test('uses default exclusion patterns', t => {
  951. t.plan(2);
  952. // Ensure each directory is treated as containing sources
  953. seed(['**/*']);
  954. // Synthesize an excluded file for each directory that's ignored by
  955. // default. Apply deeper nesting for each file.
  956. const excludedFiles = defaultIgnore.map((dir, index) => {
  957. let relPath = dir;
  958. for (let i = index; i >= 0; i--) {
  959. relPath = path.join(relPath, String(i));
  960. }
  961. return `${relPath}.js`;
  962. });
  963. // Ensure `test/1.js` also depends on the excluded files
  964. emitDependencies(
  965. path.join('test', '1.js'),
  966. excludedFiles.map(relPath => path.resolve(relPath)).concat('dep-1.js')
  967. );
  968. // Modify all excluded files
  969. excludedFiles.forEach(x => change(x));
  970. return debounce(excludedFiles.length).then(() => {
  971. t.ok(api.run.calledTwice);
  972. // Since the excluded files are not tracked as a dependency, all tests
  973. // are expected to be rerun
  974. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  975. clearLogOnNextRun: true,
  976. runVector: 2
  977. })]);
  978. });
  979. });
  980. test('allows default exclusion patterns to be overriden', t => {
  981. t.plan(2);
  982. seed(['node_modules/foo/*.js']);
  983. const dep = path.join('node_modules', 'foo', 'index.js');
  984. emitDependencies(path.join('test', '1.js'), [path.resolve(dep)]);
  985. change(dep);
  986. return debounce(1).then(() => {
  987. t.ok(api.run.calledTwice);
  988. t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, {
  989. clearLogOnNextRun: true,
  990. runVector: 2
  991. })]);
  992. });
  993. });
  994. test('ignores dependencies outside of the current working directory', t => {
  995. t.plan(4);
  996. seed(['**/*.js', '..foo.js']);
  997. emitDependencies(path.join('test', '1.js'), [path.resolve('../outside.js')]);
  998. emitDependencies(path.join('test', '2.js'), [path.resolve('..foo.js')]);
  999. // Pretend Chokidar detected a change to verify (normally Chokidar would
  1000. // also be ignoring this file but hey)
  1001. change(path.join('..', 'outside.js'));
  1002. api.run.returns(Promise.resolve(runStatus));
  1003. return debounce().then(() => {
  1004. t.ok(api.run.calledTwice);
  1005. // If `../outside.js` was tracked as a dependency of test/1.js this would
  1006. // have caused `test/1.js` to be rerun. Instead expect all tests to be
  1007. // rerun. This is somewhat artifical: normally changes to `../outside.js`
  1008. // wouldn't even be picked up. However this lets us test dependency
  1009. // tracking without directly inspecting the internal state of the
  1010. // watcher.
  1011. t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, {
  1012. clearLogOnNextRun: true,
  1013. runVector: 2
  1014. })]);
  1015. change('..foo.js');
  1016. return debounce();
  1017. }).then(() => {
  1018. t.ok(api.run.calledThrice);
  1019. t.strictDeepEqual(api.run.thirdCall.args, [[path.join('test', '2.js')], Object.assign({}, defaultApiOptions, {
  1020. clearLogOnNextRun: true,
  1021. runVector: 3
  1022. })]);
  1023. });
  1024. });
  1025. test('logs a debug message when a dependent test is found', t => {
  1026. t.plan(2);
  1027. seed();
  1028. change('dep-1.js');
  1029. return debounce().then(() => {
  1030. t.ok(debug.calledTwice);
  1031. t.strictDeepEqual(debug.secondCall.args, ['ava:watcher', '%s is a dependency of %s', 'dep-1.js', path.join('test', '1.js')]);
  1032. });
  1033. });
  1034. test('logs a debug message when sources remain without dependent tests', t => {
  1035. t.plan(3);
  1036. seed();
  1037. change('cannot-be-mapped.js');
  1038. return debounce().then(() => {
  1039. t.ok(debug.calledThrice);
  1040. t.strictDeepEqual(debug.secondCall.args, ['ava:watcher', 'Sources remain that cannot be traced to specific tests: %O', ['cannot-be-mapped.js']]);
  1041. t.strictDeepEqual(debug.thirdCall.args, ['ava:watcher', 'Rerunning all tests']);
  1042. });
  1043. });
  1044. });
  1045. group('.only is sticky', (beforeEach, test) => {
  1046. let apiEmitter;
  1047. let runStatus;
  1048. let runStatusEmitter;
  1049. beforeEach(() => {
  1050. apiEmitter = new EventEmitter();
  1051. api.on = (event, fn) => {
  1052. apiEmitter.on(event, fn);
  1053. };
  1054. runStatusEmitter = new EventEmitter();
  1055. runStatus = {
  1056. stats: {
  1057. byFile: new Map(),
  1058. declaredTests: 0,
  1059. failedHooks: 0,
  1060. failedTests: 0,
  1061. failedWorkers: 0,
  1062. files,
  1063. finishedWorkers: 0,
  1064. internalErrors: 0,
  1065. remainingTests: 0,
  1066. passedKnownFailingTests: 0,
  1067. passedTests: 0,
  1068. selectedTests: 0,
  1069. skippedTests: 0,
  1070. timeouts: 0,
  1071. todoTests: 0,
  1072. uncaughtExceptions: 0,
  1073. unhandledRejections: 0
  1074. },
  1075. on(event, fn) {
  1076. runStatusEmitter.on(event, fn);
  1077. }
  1078. };
  1079. });
  1080. const emitStats = (testFile, hasExclusive) => {
  1081. runStatus.stats.byFile.set(testFile, {
  1082. declaredTests: 2,
  1083. failedHooks: 0,
  1084. failedTests: 0,
  1085. internalErrors: 0,
  1086. remainingTests: 0,
  1087. passedKnownFailingTests: 0,
  1088. passedTests: 0,
  1089. selectedTests: hasExclusive ? 1 : 2,
  1090. skippedTests: 0,
  1091. todoTests: 0,
  1092. uncaughtExceptions: 0,
  1093. unhandledRejections: 0
  1094. });
  1095. runStatusEmitter.emit('stateChange', {type: 'worker-finished', testFile});
  1096. };
  1097. const t1 = path.join('test', '1.js');
  1098. const t2 = path.join('test', '2.js');
  1099. const t3 = path.join('test', '3.js');
  1100. const t4 = path.join('test', '4.js');
  1101. const seed = () => {
  1102. let done;
  1103. api.run.returns(new Promise(resolve => {
  1104. done = () => {
  1105. resolve(runStatus);
  1106. };
  1107. }));
  1108. const watcher = start();
  1109. apiEmitter.emit('run', {
  1110. files: [t1, t2, t3, t4],
  1111. status: runStatus
  1112. });
  1113. emitStats(t1, true);
  1114. emitStats(t2, true);
  1115. emitStats(t3, false);
  1116. emitStats(t4, false);
  1117. done();
  1118. api.run.returns(new Promise(() => {}));
  1119. return watcher;
  1120. };
  1121. test('changed test files (none of which previously contained .only) are run in exclusive mode', t => {
  1122. const options = Object.assign({}, defaultApiOptions, {runOnlyExclusive: true});
  1123. t.plan(2);
  1124. seed();
  1125. change(t3);
  1126. change(t4);
  1127. return debounce(2).then(() => {
  1128. t.ok(api.run.calledTwice);
  1129. t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t3, t4], Object.assign({}, options, {
  1130. clearLogOnNextRun: true,
  1131. runVector: 2
  1132. })]);
  1133. });
  1134. });
  1135. test('changed test files (comprising some, but not all, files that previously contained .only) are run in exclusive mode', t => {
  1136. const options = Object.assign({}, defaultApiOptions, {runOnlyExclusive: true});
  1137. t.plan(2);
  1138. seed();
  1139. change(t1);
  1140. change(t4);
  1141. return debounce(2).then(() => {
  1142. t.ok(api.run.calledTwice);
  1143. t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t4], Object.assign({}, options, {
  1144. clearLogOnNextRun: true,
  1145. runVector: 2
  1146. })]);
  1147. });
  1148. });
  1149. test('changed test files (comprising all files that previously contained .only) are run in regular mode', t => {
  1150. t.plan(2);
  1151. seed();
  1152. change(t1);
  1153. change(t2);
  1154. return debounce(2).then(() => {
  1155. t.ok(api.run.calledTwice);
  1156. t.strictDeepEqual(api.run.secondCall.args, [[t1, t2], Object.assign({}, defaultApiOptions, {
  1157. clearLogOnNextRun: true,
  1158. runVector: 2
  1159. })]);
  1160. });
  1161. });
  1162. test('once no test files contain .only, further changed test files are run in regular mode', t => {
  1163. t.plan(2);
  1164. seed();
  1165. emitStats(t1, false);
  1166. emitStats(t2, false);
  1167. change(t3);
  1168. change(t4);
  1169. return debounce(2).then(() => {
  1170. t.ok(api.run.calledTwice);
  1171. t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], Object.assign({}, defaultApiOptions, {
  1172. clearLogOnNextRun: true,
  1173. runVector: 2
  1174. })]);
  1175. });
  1176. });
  1177. test('once test files containing .only are removed, further changed test files are run in regular mode', t => {
  1178. t.plan(2);
  1179. seed();
  1180. unlink(t1);
  1181. unlink(t2);
  1182. change(t3);
  1183. change(t4);
  1184. return debounce(4).then(() => {
  1185. t.ok(api.run.calledTwice);
  1186. t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], Object.assign({}, defaultApiOptions, {
  1187. clearLogOnNextRun: true,
  1188. runVector: 2
  1189. })]);
  1190. });
  1191. });
  1192. });
  1193. group('tracks previous failures', (beforeEach, test) => {
  1194. let apiEmitter;
  1195. let runStatus;
  1196. let runStatusEmitter;
  1197. beforeEach(() => {
  1198. apiEmitter = new EventEmitter();
  1199. api.on = (event, fn) => {
  1200. apiEmitter.on(event, fn);
  1201. };
  1202. runStatusEmitter = new EventEmitter();
  1203. runStatus = {
  1204. stats: {
  1205. byFile: new Map(),
  1206. declaredTests: 0,
  1207. failedHooks: 0,
  1208. failedTests: 0,
  1209. failedWorkers: 0,
  1210. files,
  1211. finishedWorkers: 0,
  1212. internalErrors: 0,
  1213. remainingTests: 0,
  1214. passedKnownFailingTests: 0,
  1215. passedTests: 0,
  1216. selectedTests: 0,
  1217. skippedTests: 0,
  1218. timeouts: 0,
  1219. todoTests: 0,
  1220. uncaughtExceptions: 0,
  1221. unhandledRejections: 0
  1222. },
  1223. on(event, fn) {
  1224. runStatusEmitter.on(event, fn);
  1225. }
  1226. };
  1227. });
  1228. const seed = seedFailures => {
  1229. let done;
  1230. api.run.returns(new Promise(resolve => {
  1231. done = () => {
  1232. resolve(runStatus);
  1233. };
  1234. }));
  1235. const watcher = start();
  1236. const files = [path.join('test', '1.js'), path.join('test', '2.js')];
  1237. apiEmitter.emit('run', {
  1238. files,
  1239. status: runStatus
  1240. });
  1241. if (seedFailures) {
  1242. seedFailures(files);
  1243. }
  1244. done();
  1245. api.run.returns(new Promise(() => {}));
  1246. return watcher;
  1247. };
  1248. const rerun = function (file) {
  1249. runStatus = {on: runStatus.on};
  1250. let done;
  1251. api.run.returns(new Promise(resolve => {
  1252. done = () => {
  1253. resolve(runStatus);
  1254. };
  1255. }));
  1256. change(file);
  1257. return debounce().then(() => {
  1258. apiEmitter.emit('run', {
  1259. files: [file],
  1260. status: runStatus
  1261. });
  1262. done();
  1263. api.run.returns(new Promise(() => {}));
  1264. });
  1265. };
  1266. test('runs with previousFailures set to number of prevous failures', t => {
  1267. t.plan(2);
  1268. let other;
  1269. seed(files => {
  1270. runStatusEmitter.emit('stateChange', {
  1271. type: 'test-failed',
  1272. testFile: files[0]
  1273. });
  1274. runStatusEmitter.emit('stateChange', {
  1275. type: 'uncaught-exception',
  1276. testFile: files[0]
  1277. });
  1278. other = files[1];
  1279. });
  1280. return rerun(other).then(() => {
  1281. t.ok(api.run.calledTwice);
  1282. t.strictDeepEqual(api.run.secondCall.args, [[other], Object.assign({}, defaultApiOptions, {
  1283. previousFailures: 2,
  1284. clearLogOnNextRun: true,
  1285. runVector: 2
  1286. })]);
  1287. });
  1288. });
  1289. test('tracks failures from multiple files', t => {
  1290. t.plan(2);
  1291. let first;
  1292. seed(files => {
  1293. runStatusEmitter.emit('stateChange', {
  1294. type: 'test-failed',
  1295. testFile: files[0]
  1296. });
  1297. runStatusEmitter.emit('stateChange', {
  1298. type: 'test-failed',
  1299. testFile: files[1]
  1300. });
  1301. first = files[0];
  1302. });
  1303. return rerun(first).then(() => {
  1304. t.ok(api.run.calledTwice);
  1305. t.strictDeepEqual(api.run.secondCall.args, [[first], Object.assign({}, defaultApiOptions, {
  1306. previousFailures: 1,
  1307. clearLogOnNextRun: true,
  1308. runVector: 2
  1309. })]);
  1310. });
  1311. });
  1312. test('previous failures don\'t count when that file is rerun', t => {
  1313. t.plan(2);
  1314. let same;
  1315. seed(files => {
  1316. runStatusEmitter.emit('stateChange', {
  1317. type: 'test-failed',
  1318. testFile: files[0]
  1319. });
  1320. runStatusEmitter.emit('stateChange', {
  1321. type: 'uncaught-exception',
  1322. testFile: files[0]
  1323. });
  1324. same = files[0];
  1325. });
  1326. return rerun(same).then(() => {
  1327. t.ok(api.run.calledTwice);
  1328. t.strictDeepEqual(api.run.secondCall.args, [[same], Object.assign({}, defaultApiOptions, {
  1329. previousFailures: 0,
  1330. clearLogOnNextRun: true,
  1331. runVector: 2
  1332. })]);
  1333. });
  1334. });
  1335. test('previous failures don\'t count when that file is deleted', t => {
  1336. t.plan(2);
  1337. let same;
  1338. let other;
  1339. seed(files => {
  1340. runStatusEmitter.emit('stateChange', {
  1341. type: 'test-failed',
  1342. testFile: files[0]
  1343. });
  1344. runStatusEmitter.emit('stateChange', {
  1345. type: 'uncaught-exception',
  1346. testFile: files[0]
  1347. });
  1348. same = files[0];
  1349. other = files[1];
  1350. });
  1351. unlink(same);
  1352. return debounce().then(() => rerun(other)).then(() => {
  1353. t.ok(api.run.calledTwice);
  1354. t.strictDeepEqual(api.run.secondCall.args, [[other], Object.assign({}, defaultApiOptions, {
  1355. previousFailures: 0,
  1356. clearLogOnNextRun: true,
  1357. runVector: 2
  1358. })]);
  1359. });
  1360. });
  1361. });
  1362. });