1
+ /* eslint-disable one-var */
1
2
/**
2
3
* @fileOverview
3
4
*
4
5
* This module externally sets up the test runner on pm api. Essentially, it does not know the insides of pm-api and
5
6
* does the job completely from outside with minimal external dependency
6
7
*/
7
- const FUNCTION = 'function' ;
8
+ const _ = require ( 'lodash' ) ,
9
+ FUNCTION = 'function' ,
10
+ uuid = require ( '../vendor/uuid' ) ,
11
+
12
+ OPTIONS = {
13
+ When : 'when' ,
14
+ RunCount : 'runCount' ,
15
+ RunUntil : 'runUntil'
16
+ } ,
17
+ OPTION_TYPE = {
18
+ [ OPTIONS . When ] : 'function' ,
19
+ [ OPTIONS . RunCount ] : 'number' ,
20
+ [ OPTIONS . RunUntil ] : 'number'
21
+ } ;
8
22
9
23
/**
10
24
* @module {PMAPI~setupTestRunner}
11
25
* @private
12
26
*
13
27
* @param {PMAPI } pm - an instance of PM API that it needs
14
- * @param {Function } onAssertionComplete - is the trigger function that is called every time a test is executed and it
28
+ * @param {Object } testsState - State of all the tests for the current execution
29
+ * @param {Function } onAssertion - is the trigger function that is called every time a test is encountered and it
15
30
* receives the AssertionInfo object outlining details of the assertion
16
31
*/
17
- module . exports = function ( pm , onAssertionComplete ) {
32
+ module . exports = function ( pm , testsState , onAssertion ) {
18
33
var assertionIndex = 0 ,
19
34
20
35
/**
@@ -25,24 +40,73 @@ module.exports = function (pm, onAssertionComplete) {
25
40
*
26
41
* @param {String } name -
27
42
* @param {Boolean } skipped -
43
+ * @param {String } [id] -
28
44
*
29
45
* @returns {PMAPI~AssertionInfo }
30
46
*/
31
- getAssertionObject = function ( name , skipped ) {
47
+ getAssertionObject = function ( name , skipped , id ) {
48
+ if ( ! id ) {
49
+ id = uuid ( ) ;
50
+ }
51
+
32
52
/**
33
53
* @typeDef {AssertionInfo}
34
54
* @private
35
55
*/
36
56
return {
57
+ id : id ,
37
58
name : String ( name ) ,
38
59
async : false ,
39
60
skipped : Boolean ( skipped ) ,
40
61
passed : true ,
62
+ pending : ! skipped ,
41
63
error : null ,
42
64
index : assertionIndex ++ // increment the assertion counter (do it before asserting)
43
65
} ;
44
66
} ,
45
67
68
+ generateTestId = function ( eventName , testName , assertFn , options ) {
69
+ return [
70
+ eventName ,
71
+ testName ,
72
+ assertFn ? assertFn . toString ( ) : '' ,
73
+ JSON . stringify ( options )
74
+ ] . join ( '' ) ;
75
+ } ,
76
+
77
+ getDefaultTestState = function ( options ) {
78
+ return {
79
+ ...( options ? _ . pick ( options , _ . values ( OPTIONS ) ) : { } ) ,
80
+ id : uuid ( ) ,
81
+ timer : null ,
82
+ currRunCount : 0 ,
83
+ resolved : false
84
+ } ;
85
+ } ,
86
+
87
+ isOptionConfigured = function ( options , optionName ) {
88
+ return _ . has ( options , optionName ) && typeof options [ optionName ] === OPTION_TYPE [ optionName ] ;
89
+ } ,
90
+
91
+
92
+ validateOptions = function ( options ) {
93
+ if ( ! options || typeof options !== 'object' ) {
94
+ throw new Error ( 'Invalid test option: options is not an object' ) ;
95
+ }
96
+
97
+ const supportedOptions = _ . values ( OPTIONS ) ;
98
+
99
+ Object . keys ( options ) . forEach ( ( optionName ) => {
100
+ if ( ! supportedOptions . includes ( optionName ) ) {
101
+ throw new Error ( `Invalid test option: ${ optionName } is not a supported option` ) ;
102
+ }
103
+
104
+ if ( typeof options [ optionName ] !== OPTION_TYPE [ optionName ] ) {
105
+ throw new Error ( `Invalid test options: ${ optionName } is not a ${ OPTION_TYPE [ optionName ] } ` ) ;
106
+ }
107
+ } ) ;
108
+ } ,
109
+
46
110
/**
47
111
* Simple function to mark an assertion as failed
48
112
*
@@ -57,59 +121,135 @@ module.exports = function (pm, onAssertionComplete) {
57
121
markAssertionAsFailure = function ( assertionData , err ) {
58
122
assertionData . error = err ;
59
123
assertionData . passed = false ;
124
+ } ,
125
+
126
+ processAssertion = function ( _testId , assertionData , options ) {
127
+ const testState = testsState [ _testId ] ;
128
+
129
+ if ( testState . resolved ) {
130
+ return ;
131
+ }
132
+
133
+ const shouldResolve =
134
+ assertionData . error || // TODO: Make conditions (test status) to mark a test resolved, configurable.
135
+ assertionData . skipped ||
136
+ _ . isEmpty ( options ) ||
137
+ ! testState ||
138
+ isOptionConfigured ( options , OPTIONS . RunCount ) && testState . runCount === testState . currRunCount ||
139
+ isOptionConfigured ( options , OPTIONS . RunUntil ) && ! testState . timer ;
140
+
141
+
142
+ testState . resolved = shouldResolve ;
143
+ assertionData . pending = ! shouldResolve ;
144
+
145
+ onAssertion ( assertionData , shouldResolve ) ;
146
+ } ,
147
+
148
+ updateTestState = function ( _testId , assertionData , options ) {
149
+ const testState = testsState [ _testId ] ,
150
+ startTimer = ! testState . timer && isOptionConfigured ( options , OPTIONS . RunUntil ) ;
151
+
152
+ testState . currRunCount ++ ;
153
+
154
+ if ( startTimer ) {
155
+ testState . timer = setTimeout ( ( ) => {
156
+ testState . timer = null ;
157
+ processAssertion ( _testId , assertionData , options ) ;
158
+ } , testState . runUntil ) ;
159
+ }
60
160
} ;
61
161
62
162
/**
63
163
* @param {String } name -
164
+ * @param {Object } [options] -
64
165
* @param {Function } assert -
65
166
* @chainable
66
167
*/
67
- pm . test = function ( name , assert ) {
68
- var assertionData = getAssertionObject ( name , false ) ;
168
+ pm . test = function ( name , options , assert ) {
169
+ if ( typeof options === FUNCTION ) {
170
+ assert = options ;
171
+ options = { } ;
172
+ }
173
+
174
+ if ( _ . isNil ( options ) || typeof options !== 'object' ) {
175
+ options = { } ;
176
+ }
177
+
178
+ // TODO: Make generateTestId safe i.e handle invalid `options` as well
179
+ const _testId = generateTestId ( pm . info . eventName , name , assert , options ) ;
180
+
181
+ if ( ! testsState [ _testId ] ) {
182
+ testsState [ _testId ] = getDefaultTestState ( options ) ;
183
+ }
184
+
185
+ const testState = testsState [ _testId ] ,
186
+ testId = testState . testId ,
187
+ assertionData = getAssertionObject ( name , false , testId ) ;
69
188
70
189
// if there is no assertion function, we simply move on
71
190
if ( typeof assert !== FUNCTION ) {
72
- onAssertionComplete ( assertionData ) ;
191
+ // Sending `options` as empty to force resolve the test
192
+ processAssertion ( _testId , assertionData , { } ) ;
73
193
74
194
return pm ;
75
195
}
76
196
77
- // if a callback function was sent, then we know that the test is asynchronous
78
- if ( assert . length ) {
79
- try {
80
- assertionData . async = true ; // flag that this was an async test (would be useful later)
81
-
82
- // we execute assertion, but pass it a completion function, which, in turn, raises the completion
83
- // event. we do not need to worry about timers here since we are assuming that some timer within the
84
- // sandbox had actually been the source of async calls and would take care of this
85
- assert ( function ( err ) {
86
- // at first we double check that no synchronous error has happened from the catch block below
87
- if ( assertionData . error && assertionData . passed === false ) {
88
- return ;
89
- }
90
-
91
- // user triggered a failure of the assertion, so we mark it the same
92
- if ( err ) {
93
- markAssertionAsFailure ( assertionData , err ) ;
94
- }
95
-
96
- onAssertionComplete ( assertionData ) ;
97
- } ) ;
197
+ try { validateOptions ( options ) ; }
198
+ catch ( e ) {
199
+ markAssertionAsFailure ( assertionData , e ) ;
200
+ processAssertion ( _testId , assertionData , options ) ;
201
+
202
+ return pm ;
203
+ }
204
+
205
+ const shouldRun =
206
+ ! testState . resolved &&
207
+ ( isOptionConfigured ( options , OPTIONS . When ) ? Boolean ( options . when ( ) ) : true ) &&
208
+ ( isOptionConfigured ( options , OPTIONS . RunCount ) ? testState . currRunCount < options . runCount : true ) ;
209
+
210
+ if ( shouldRun ) {
211
+ updateTestState ( _testId , assertionData , options ) ;
212
+
213
+ // if a callback function was sent, then we know that the test is asynchronous
214
+ if ( assert . length ) {
215
+ try {
216
+ assertionData . async = true ; // flag that this was an async test (would be useful later)
217
+
218
+ // we execute assertion, but pass it a completion function, which, in turn, raises the completion
219
+ // event. we do not need to worry about timers here since we are assuming that some timer within the
220
+ // sandbox had actually been the source of async calls and would take care of this
221
+ assert ( function ( err ) {
222
+ // at first we double check that no synchronous error has happened from the catch block below
223
+ if ( assertionData . error && assertionData . passed === false ) {
224
+ return ;
225
+ }
226
+
227
+ // user triggered a failure of the assertion, so we mark it the same
228
+ if ( err ) {
229
+ markAssertionAsFailure ( assertionData , err ) ;
230
+ }
231
+
232
+ processAssertion ( _testId , assertionData , options ) ;
233
+ } ) ;
234
+ }
235
+ // in case a synchronous error occurs in the the async assertion, we still bail out.
236
+ catch ( e ) {
237
+ markAssertionAsFailure ( assertionData , e ) ;
238
+ processAssertion ( _testId , assertionData , options ) ;
239
+ }
98
240
}
99
- // in case a synchronous error occurs in the the async assertion, we still bail out.
100
- catch ( e ) {
101
- markAssertionAsFailure ( assertionData , e ) ;
102
- onAssertionComplete ( assertionData ) ;
241
+ // if the assertion function does not expect a callback, we synchronously execute the same
242
+ else {
243
+ try { assert ( ) ; }
244
+ catch ( e ) {
245
+ markAssertionAsFailure ( assertionData , e ) ;
246
+ }
247
+
248
+ processAssertion ( _testId , assertionData , options ) ;
103
249
}
104
250
}
105
- // if the assertion function does not expect a callback, we synchronously execute the same
106
251
else {
107
- try { assert ( ) ; }
108
- catch ( e ) {
109
- markAssertionAsFailure ( assertionData , e ) ;
110
- }
111
-
112
- onAssertionComplete ( assertionData ) ;
252
+ processAssertion ( _testId , assertionData , options ) ;
113
253
}
114
254
115
255
return pm ; // make it chainable
@@ -121,7 +261,7 @@ module.exports = function (pm, onAssertionComplete) {
121
261
*/
122
262
pm . test . skip = function ( name ) {
123
263
// trigger the assertion events with skips
124
- onAssertionComplete ( getAssertionObject ( name , true ) ) ;
264
+ processAssertion ( name , getAssertionObject ( name , true ) , { } ) ;
125
265
126
266
return pm ; // chainable
127
267
} ;
0 commit comments