@@ -4,9 +4,6 @@ const fs = require('fs-extra');
44const os = require ( 'os' ) ;
55const path = require ( 'path' ) ;
66const helpers = require ( '../helpers' ) ;
7- const runMochaJSONRawAsync = helpers . runMochaJSONRawAsync ;
8-
9- const sigintExitCode = 130 ;
107
118describe ( '--watch' , function ( ) {
129 describe ( 'when enabled' , function ( ) {
@@ -15,11 +12,6 @@ describe('--watch', function() {
1512
1613 beforeEach ( function ( ) {
1714 this . tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'mocha-' ) ) ;
18-
19- const fixtureSource = helpers . DEFAULT_FIXTURE ;
20-
21- this . testFile = path . join ( this . tempDir , 'test.js' ) ;
22- fs . copySync ( fixtureSource , this . testFile ) ;
2315 } ) ;
2416
2517 afterEach ( function ( ) {
@@ -28,79 +20,39 @@ describe('--watch', function() {
2820 }
2921 } ) ;
3022
31- it ( 'should show the cursor and signal correct exit code, when watch process is terminated' , function ( ) {
32- // Feature works but SIMULATING the signal (ctrl+c) via child process
33- // does not work due to lack of POSIX signal compliance on Windows.
34- if ( process . platform === 'win32' ) {
35- this . skip ( ) ;
36- }
37-
38- const [ mocha , resultPromise ] = runMochaJSONRawAsync ( [
39- helpers . DEFAULT_FIXTURE ,
40- '--watch'
41- ] ) ;
42-
43- return sleep ( 1000 )
44- . then ( ( ) => {
45- mocha . kill ( 'SIGINT' ) ;
46- return resultPromise ;
47- } )
48- . then ( data => {
49- const expectedCloseCursor = '\u001b[?25h' ;
50- expect ( data . output , 'to contain' , expectedCloseCursor ) ;
51-
52- expect ( data . code , 'to be' , sigintExitCode ) ;
53- } ) ;
54- } ) ;
55-
5623 it ( 'reruns test when watched test file is touched' , function ( ) {
57- const [ mocha , outputPromise ] = runMochaJSONWatchAsync ( [ this . testFile ] , {
58- cwd : this . tempDir
59- } ) ;
24+ const testFile = path . join ( this . tempDir , 'test.js' ) ;
25+ copyFixture ( '__default__' , testFile ) ;
6026
61- return expect (
62- sleep ( 1000 )
63- . then ( ( ) => {
64- touchFile ( this . testFile ) ;
65- return sleep ( 1000 ) ;
66- } )
67- . then ( ( ) => {
68- mocha . kill ( 'SIGINT' ) ;
69- return outputPromise ;
70- } ) ,
71- 'when fulfilled' ,
72- 'to have length' ,
73- 2
74- ) ;
27+ return runMochaWatch ( [ testFile ] , this . tempDir , ( ) => {
28+ touchFile ( testFile ) ;
29+ } ) . then ( results => {
30+ expect ( results , 'to have length' , 2 ) ;
31+ } ) ;
7532 } ) ;
7633
7734 it ( 'reruns test when file matching extension is touched' , function ( ) {
35+ const testFile = path . join ( this . tempDir , 'test.js' ) ;
36+ copyFixture ( '__default__' , testFile ) ;
37+
7838 const watchedFile = path . join ( this . tempDir , 'file.xyz' ) ;
7939 touchFile ( watchedFile ) ;
80- const [ mocha , outputPromise ] = runMochaJSONWatchAsync (
81- [ this . testFile , '--extension' , 'xyz,js' ] ,
82- {
83- cwd : this . tempDir
84- }
85- ) ;
8640
87- return expect (
88- sleep ( 1000 )
89- . then ( ( ) => {
90- touchFile ( watchedFile ) ;
91- return sleep ( 1000 ) ;
92- } )
93- . then ( ( ) => {
94- mocha . kill ( 'SIGINT' ) ;
95- return outputPromise ;
96- } ) ,
97- 'when fulfilled' ,
98- 'to have length' ,
99- 2
100- ) ;
41+ return runMochaWatch (
42+ [ testFile , '--extension' , 'xyz,js' ] ,
43+ this . tempDir ,
44+ ( ) => {
45+ touchFile ( watchedFile ) ;
46+ }
47+ ) . then ( results => {
48+ expect ( results , 'to have length' , 2 ) ;
49+ } ) ;
10150 } ) ;
10251
103- it ( 'ignores files in "node_modules" and ".git"' , function ( ) {
52+ it ( 'ignores files in "node_modules" and ".git" by default' , function ( ) {
53+ const testFile = path . join ( this . tempDir , 'test.js' ) ;
54+ copyFixture ( '__default__' , testFile ) ;
55+
10456 const nodeModulesFile = path . join (
10557 this . tempDir ,
10658 'node_modules' ,
@@ -111,50 +63,91 @@ describe('--watch', function() {
11163 touchFile ( gitFile ) ;
11264 touchFile ( nodeModulesFile ) ;
11365
114- const [ mocha , outputPromise ] = runMochaJSONWatchAsync (
115- [ this . testFile , '--extension' , 'xyz,js' ] ,
116- {
117- cwd : this . tempDir
66+ return runMochaWatch (
67+ [ testFile , '--extension' , 'xyz,js' ] ,
68+ this . tempDir ,
69+ ( ) => {
70+ touchFile ( gitFile ) ;
71+ touchFile ( nodeModulesFile ) ;
11872 }
119- ) ;
73+ ) . then ( results => {
74+ expect ( results , 'to have length' , 1 ) ;
75+ } ) ;
76+ } ) ;
12077
121- return expect (
122- sleep ( 1000 )
123- . then ( ( ) => {
124- touchFile ( gitFile ) ;
125- touchFile ( nodeModulesFile ) ;
126- } )
127- . then ( ( ) => sleep ( 1000 ) )
128- . then ( ( ) => {
129- mocha . kill ( 'SIGINT' ) ;
130- return outputPromise ;
131- } ) ,
132- 'when fulfilled' ,
133- 'to have length' ,
134- 1
135- ) ;
78+ it ( 'reloads test files when they change' , function ( ) {
79+ const testFile = path . join ( this . tempDir , 'test.js' ) ;
80+ copyFixture ( 'options/watch/test-file-change' , testFile ) ;
81+
82+ return runMochaWatch ( [ testFile ] , this . tempDir , ( ) => {
83+ replaceFileContents (
84+ testFile ,
85+ 'testShouldFail = true' ,
86+ 'testShouldFail = false'
87+ ) ;
88+ } ) . then ( results => {
89+ expect ( results , 'to have length' , 2 ) ;
90+ expect ( results [ 0 ] . passes , 'to have length' , 0 ) ;
91+ expect ( results [ 0 ] . failures , 'to have length' , 1 ) ;
92+ expect ( results [ 1 ] . passes , 'to have length' , 1 ) ;
93+ expect ( results [ 1 ] . failures , 'to have length' , 0 ) ;
94+ } ) ;
95+ } ) ;
96+
97+ it ( 'reloads test dependencies when they change' , function ( ) {
98+ const testFile = path . join ( this . tempDir , 'test.js' ) ;
99+ copyFixture ( 'options/watch/test-with-dependency' , testFile ) ;
100+
101+ const dependency = path . join ( this . tempDir , 'lib' , 'dependency.js' ) ;
102+ copyFixture ( 'options/watch/dependency' , dependency ) ;
103+
104+ return runMochaWatch ( [ testFile ] , this . tempDir , ( ) => {
105+ replaceFileContents (
106+ dependency ,
107+ 'module.exports.testShouldFail = false' ,
108+ 'module.exports.testShouldFail = true'
109+ ) ;
110+ } ) . then ( results => {
111+ expect ( results , 'to have length' , 2 ) ;
112+ expect ( results [ 0 ] . passes , 'to have length' , 1 ) ;
113+ expect ( results [ 0 ] . failures , 'to have length' , 0 ) ;
114+ expect ( results [ 1 ] . passes , 'to have length' , 0 ) ;
115+ expect ( results [ 1 ] . failures , 'to have length' , 1 ) ;
116+ } ) ;
136117 } ) ;
137118 } ) ;
138119} ) ;
139120
140121/**
141- * Invokes the mocha binary with the `--watch` argument for the given fixture.
122+ * Runs the mocha binary in watch mode calls `change` and returns the
123+ * JSON reporter output.
142124 *
143- * Returns child process and a promise for the test results. The test results
144- * are an array of JSON objects generated by the JSON reporter.
125+ * The function starts mocha with the given arguments and `--watch` and
126+ * waits until the first test run has completed. Then it calls `change`
127+ * and waits until the second test run has been completed. Mocha is
128+ * killed and the list of JSON outputs is returned.
145129 */
146- function runMochaJSONWatchAsync ( args , spawnOpts ) {
147- args = [ ...args , '--watch' ] ;
148- const [ mocha , mochaDone ] = runMochaJSONRawAsync ( args , spawnOpts ) ;
149- const testResults = mochaDone . then ( data => {
150- const testResults = data . output
151- // eslint-disable-next-line no-control-regex
152- . replace ( / \u001b \[ \? 2 5 ./ g, '' )
153- . split ( '\u001b[2K' )
154- . map ( x => JSON . parse ( x ) ) ;
155- return testResults ;
156- } ) ;
157- return [ mocha , testResults ] ;
130+ function runMochaWatch ( args , cwd , change ) {
131+ const [ mochaProcess , resultPromise ] = helpers . invokeMochaAsync (
132+ [ ...args , '--watch' , '--reporter' , 'json' ] ,
133+ { cwd}
134+ ) ;
135+
136+ return sleep ( 1000 )
137+ . then ( ( ) => change ( ) )
138+ . then ( ( ) => sleep ( 1000 ) )
139+ . then ( ( ) => {
140+ mochaProcess . kill ( 'SIGINT' ) ;
141+ return resultPromise ;
142+ } )
143+ . then ( data => {
144+ const testResults = data . output
145+ // eslint-disable-next-line no-control-regex
146+ . replace ( / \u001b \[ \? 2 5 ./ g, '' )
147+ . split ( '\u001b[2K' )
148+ . map ( x => JSON . parse ( x ) ) ;
149+ return testResults ;
150+ } ) ;
158151}
159152
160153/**
@@ -166,6 +159,26 @@ function touchFile(file) {
166159 fs . appendFileSync ( file , ' ' ) ;
167160}
168161
162+ /**
163+ * Synchronously eplace all substrings matched by `pattern` with
164+ * `replacement` in the file’s content.
165+ */
166+ function replaceFileContents ( file , pattern , replacement ) {
167+ const contents = fs . readFileSync ( file , 'utf-8' ) ;
168+ const newContents = contents . replace ( pattern , replacement ) ;
169+ fs . writeFileSync ( file , newContents , 'utf-8' ) ;
170+ }
171+
172+ /**
173+ * Synchronously copy a fixture to the given destion file path. Creates
174+ * parent directories of the destination path if necessary.
175+ */
176+ function copyFixture ( fixtureName , dest ) {
177+ const fixtureSource = helpers . resolveFixturePath ( fixtureName ) ;
178+ fs . ensureDirSync ( path . dirname ( dest ) ) ;
179+ fs . copySync ( fixtureSource , dest ) ;
180+ }
181+
169182function sleep ( time ) {
170183 return new Promise ( resolve => {
171184 setTimeout ( resolve , time ) ;
0 commit comments