UNPKG

6.99 kBtext/coffeescriptView Raw
1{util, deferred, parallel} = require 'also'
2{AssertionError} = require 'assert'
3facto = require 'facto'
4Loader = require './loader'
5colors = require 'colors'
6Does = require 'does'
7does = Does does: mode: 'spec'
8should = require 'should'
9
10config =
11
12 #
13 # **ipso should be run in repo root**
14 #
15
16 dir: process.cwd()
17 modules: {}
18
19{loadModules, loadModulesSync} = Loader.create config
20
21#
22# `ipso( testFunction )` - Decorates a test function
23# --------------------------------------------------
24#
25# * All ipso tests are asynchronous - done is called in the background on the nextTick
26# if the testFunction signature itself did not contain 'done' at argument1
27#
28
29module.exports = ipso = (actualTestFunction) ->
30
31 return testFunctionForMocha = (done) ->
32
33 fnArgsArray = util.argsOf actualTestFunction
34
35 argsToInjectIntoTest = []
36
37 unless done?
38
39 #
40 # ### Injecting into describe() or context()
41 #
42
43 if fnArgsArray[0] is 'done' or fnArgsArray[0] is 'facto'
44
45 console.log 'ipso cannot inject done into describe() or context()'.red
46 return
47
48 does.activate context: @, mode: 'spec', spec: null, resolver: null
49 argsToInjectIntoTest.push Module for Module in loadModulesSync( fnArgsArray, does )
50 actualTestFunction.apply @, argsToInjectIntoTest
51 return
52
53
54
55
56 #
57 # ### Injecting into hook or it()
58 #
59
60 does.activate context: @, mode: 'spec', spec: @test, resolver: done
61
62
63 #
64 # * testResolver wraps mocha's done into a proxy that call it via
65 # does.asset(... for function expectations that mocha is not aware of.
66 #
67
68 testResolver = (metadata) ->
69
70 does.assert( done ).then(
71
72 (result) ->
73
74 #
75 # * does.assert(... does not call done if nothing failed
76 #
77
78 if fnArgsArray[0] is 'facto' then facto metadata
79 done()
80
81
82 (error) ->
83
84 #
85 # * does.assert(... already called done - to fail the mocha test
86 #
87
88 if fnArgsArray[0] is 'facto' then facto metadata
89
90 (notify) ->
91
92 #
93 # * later...
94 #
95
96 )
97
98 #
99 # * testResolver is only injected if arg1 is done or facto
100 #
101
102 if fnArgsArray[0] is 'done' or fnArgsArray[0] is 'facto'
103
104 argsToInjectIntoTest.push testResolver
105 arg1 = fnArgsArray.shift()
106
107 loadModules( fnArgsArray, does ).then(
108
109 #
110 # * loader resolved with list of Modules refs to inject
111 #
112
113 (Modules) =>
114
115 argsToInjectIntoTest.push Module for Module in Modules
116
117 try promise = actualTestFunction.apply @, argsToInjectIntoTest
118 catch error
119
120 does.reset().then -> done error
121 return
122
123 if arg1 isnt 'done' and arg1 isnt 'facto'
124
125 #
126 # * test did not "request" done or facto (ie. synchronous)
127 # but this test wrapper got a done from mocha, it needs
128 # to be called.
129 #
130
131 try if promise.then? and @test.type is 'test'
132 return does.reset().then ->
133 done new Error 'Synchronous test returned promise. Inject test resolver (done or facto).'
134
135
136 testResolver()
137 return
138
139 #
140 # * redirect AssertionError being raised in a promise chain
141 # back into mocha's test resolver
142 #
143
144 try if promise.then? then promise.then (->), (error) ->
145
146 does.reset().then -> done error
147
148
149 #
150 # * loader rejection into done() - error loading module
151 #
152
153 (error) ->
154
155 does.reset().then -> done error
156
157
158 )
159
160
161#
162# convenience {ipso, mock, tag} = require 'ipso'
163#
164
165ipso.ipso = ipso
166ipso.mock = (name) ->
167
168 object =
169 title: name
170 is: (mock) ->
171 if typeof mock is 'object' then return object.should.equal mock
172 name.should.equal mock
173
174 #
175 # experiment - may become property expetations
176 #
177
178 with: (list) ->
179
180 object[key] = list[key] for key of list
181 return object
182
183 #
184 # TODO: tagged?
185 #
186
187 return does.spectateSync name: name, tagged: true, object
188
189
190ipso.Mock = (name) ->
191
192 #
193 # Mock() (with capital M) mocks a class definition.
194 #
195 # !!EXPERIMENT!!, pending a properly implemented method
196 # that stubs the prototype
197 #
198 #
199
200 #
201 # * create the mock for injection into subsequent hooks and
202 # tests where .does() can be called upon to create future
203 # instanceMethod expectations.
204 #
205
206 mockObject = ipso.mock name
207
208 return klass = class
209
210 #
211 # * with() as class method to configure the base set
212 # of function and property stubs for each future
213 # instance of the class
214 #
215
216 @with = ->
217
218 mockObject.with.apply @, arguments
219 return klass
220
221 #
222 # assemble instance from current stub set
223 # ---------------------------------------
224 #
225 # * will contain stubs (properties and functions) as set
226 # up in the mock().with()
227 #
228 # * will also includes further ammendments created with
229 # .does() in subsequent hooks (or in the test itself)
230 #
231
232 constructor: ->
233
234 stubs = does.getSync(name).object
235
236 #
237 # * run the special case constructor spy if present
238 #
239
240 if typeof stubs.$constructor is 'function'
241
242 stubs.$constructor.apply @, arguments
243
244 #
245 # * create the object properties and function from
246 # the current contents of the mock stub set.
247 #
248
249 for stub of stubs
250
251 continue if stub is '$constructor'
252 @[stub] = stubs[stub]
253
254
255
256ipso.tag = deferred (action, list) ->
257
258 #
259 # not necessary to carry the promise, this is a synchronous call
260 # but remains potentially async for future use
261 #
262
263 parallel( for tag of list
264
265 do (tag) -> -> does.spectateSync
266
267 name: tag
268 tagged: true
269 list[tag]
270
271 ).then action.resolve, action.reject, action.notify
272
273
274ipso.define = require './define'
275
276
277ipso.does = does
278
279
280module.exports.once = (fn) -> do (done = false) -> ->
281
282 return if done
283 done = true
284 fn.apply @, arguments
285
286