1 /**
  2  * Internal representation of a Jasmine specification, or test.
  3  *
  4  * @constructor
  5  * @param {jasmine.Env} env
  6  * @param {jasmine.Suite} suite
  7  * @param {String} description
  8  */
  9 jasmine.Spec = function(env, suite, description) {
 10   if (!env) {
 11     throw new Error('jasmine.Env() required');
 12   }
 13   if (!suite) {
 14     throw new Error('jasmine.Suite() required');
 15   }
 16   var spec = this;
 17   spec.id = env.nextSpecId ? env.nextSpecId() : null;
 18   spec.env = env;
 19   spec.suite = suite;
 20   spec.description = description;
 21   spec.queue = new jasmine.Queue(env);
 22 
 23   spec.afterCallbacks = [];
 24   spec.spies_ = [];
 25 
 26   spec.results_ = new jasmine.NestedResults();
 27   spec.results_.description = description;
 28   spec.matchersClass = null;
 29 };
 30 
 31 jasmine.Spec.prototype.getFullName = function() {
 32   return this.suite.getFullName() + ' ' + this.description + '.';
 33 };
 34 
 35 
 36 jasmine.Spec.prototype.results = function() {
 37   return this.results_;
 38 };
 39 
 40 /**
 41  * All parameters are pretty-printed and concatenated together, then written to the spec's output.
 42  *
 43  * Be careful not to leave calls to <code>jasmine.log</code> in production code.
 44  */
 45 jasmine.Spec.prototype.log = function() {
 46   return this.results_.log(arguments);
 47 };
 48 
 49 jasmine.Spec.prototype.runs = function (func) {
 50   var block = new jasmine.Block(this.env, func, this);
 51   this.addToQueue(block);
 52   return this;
 53 };
 54 
 55 jasmine.Spec.prototype.addToQueue = function (block) {
 56   if (this.queue.isRunning()) {
 57     this.queue.insertNext(block);
 58   } else {
 59     this.queue.add(block);
 60   }
 61 };
 62 
 63 /**
 64  * @param {jasmine.ExpectationResult} result
 65  */
 66 jasmine.Spec.prototype.addMatcherResult = function(result) {
 67   this.results_.addResult(result);
 68 };
 69 
 70 jasmine.Spec.prototype.expect = function(actual) {
 71   var positive = new (this.getMatchersClass_())(this.env, actual, this);
 72   positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
 73   return positive;
 74 };
 75 
 76 /**
 77  * Waits a fixed time period before moving to the next block.
 78  *
 79  * @deprecated Use waitsFor() instead
 80  * @param {Number} timeout milliseconds to wait
 81  */
 82 jasmine.Spec.prototype.waits = function(timeout) {
 83   var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
 84   this.addToQueue(waitsFunc);
 85   return this;
 86 };
 87 
 88 /**
 89  * Waits for the latchFunction to return true before proceeding to the next block.
 90  *
 91  * @param {Function} latchFunction
 92  * @param {String} optional_timeoutMessage
 93  * @param {Number} optional_timeout
 94  */
 95 jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
 96   var latchFunction_ = null;
 97   var optional_timeoutMessage_ = null;
 98   var optional_timeout_ = null;
 99 
100   for (var i = 0; i < arguments.length; i++) {
101     var arg = arguments[i];
102     switch (typeof arg) {
103       case 'function':
104         latchFunction_ = arg;
105         break;
106       case 'string':
107         optional_timeoutMessage_ = arg;
108         break;
109       case 'number':
110         optional_timeout_ = arg;
111         break;
112     }
113   }
114 
115   var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
116   this.addToQueue(waitsForFunc);
117   return this;
118 };
119 
120 jasmine.Spec.prototype.fail = function (e) {
121   var expectationResult = new jasmine.ExpectationResult({
122     passed: false,
123     message: e ? jasmine.util.formatException(e) : 'Exception'
124   });
125   this.results_.addResult(expectationResult);
126 };
127 
128 jasmine.Spec.prototype.getMatchersClass_ = function() {
129   return this.matchersClass || this.env.matchersClass;
130 };
131 
132 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
133   var parent = this.getMatchersClass_();
134   var newMatchersClass = function() {
135     parent.apply(this, arguments);
136   };
137   jasmine.util.inherit(newMatchersClass, parent);
138   jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
139   this.matchersClass = newMatchersClass;
140 };
141 
142 jasmine.Spec.prototype.finishCallback = function() {
143   this.env.reporter.reportSpecResults(this);
144 };
145 
146 jasmine.Spec.prototype.finish = function(onComplete) {
147   this.removeAllSpies();
148   this.finishCallback();
149   if (onComplete) {
150     onComplete();
151   }
152 };
153 
154 jasmine.Spec.prototype.after = function(doAfter) {
155   if (this.queue.isRunning()) {
156     this.queue.add(new jasmine.Block(this.env, doAfter, this));
157   } else {
158     this.afterCallbacks.unshift(doAfter);
159   }
160 };
161 
162 jasmine.Spec.prototype.execute = function(onComplete) {
163   var spec = this;
164   if (!spec.env.specFilter(spec)) {
165     spec.results_.skipped = true;
166     spec.finish(onComplete);
167     return;
168   }
169 
170   this.env.reporter.reportSpecStarting(this);
171 
172   spec.env.currentSpec = spec;
173 
174   spec.addBeforesAndAftersToQueue();
175 
176   spec.queue.start(function () {
177     spec.finish(onComplete);
178   });
179 };
180 
181 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
182   var runner = this.env.currentRunner();
183   var i;
184 
185   for (var suite = this.suite; suite; suite = suite.parentSuite) {
186     for (i = 0; i < suite.before_.length; i++) {
187       this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
188     }
189   }
190   for (i = 0; i < runner.before_.length; i++) {
191     this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
192   }
193   for (i = 0; i < this.afterCallbacks.length; i++) {
194     this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
195   }
196   for (suite = this.suite; suite; suite = suite.parentSuite) {
197     for (i = 0; i < suite.after_.length; i++) {
198       this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
199     }
200   }
201   for (i = 0; i < runner.after_.length; i++) {
202     this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
203   }
204 };
205 
206 jasmine.Spec.prototype.explodes = function() {
207   throw 'explodes function should not have been called';
208 };
209 
210 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
211   if (obj == jasmine.undefined) {
212     throw "spyOn could not find an object to spy upon for " + methodName + "()";
213   }
214 
215   if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
216     throw methodName + '() method does not exist';
217   }
218 
219   if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
220     throw new Error(methodName + ' has already been spied upon');
221   }
222 
223   var spyObj = jasmine.createSpy(methodName);
224 
225   this.spies_.push(spyObj);
226   spyObj.baseObj = obj;
227   spyObj.methodName = methodName;
228   spyObj.originalValue = obj[methodName];
229 
230   obj[methodName] = spyObj;
231 
232   return spyObj;
233 };
234 
235 jasmine.Spec.prototype.removeAllSpies = function() {
236   for (var i = 0; i < this.spies_.length; i++) {
237     var spy = this.spies_[i];
238     spy.baseObj[spy.methodName] = spy.originalValue;
239   }
240   this.spies_ = [];
241 };
242 
243