1 /**
  2  * Environment for Jasmine
  3  *
  4  * @constructor
  5  */
  6 jasmine.Env = function() {
  7   this.currentSpec = null;
  8   this.currentSuite = null;
  9   this.currentRunner_ = new jasmine.Runner(this);
 10 
 11   this.reporter = new jasmine.MultiReporter();
 12 
 13   this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
 14   this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
 15   this.lastUpdate = 0;
 16   this.specFilter = function() {
 17     return true;
 18   };
 19 
 20   this.nextSpecId_ = 0;
 21   this.nextSuiteId_ = 0;
 22   this.equalityTesters_ = [];
 23 
 24   // wrap matchers
 25   this.matchersClass = function() {
 26     jasmine.Matchers.apply(this, arguments);
 27   };
 28   jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
 29 
 30   jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
 31 };
 32 
 33 
 34 jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
 35 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
 36 jasmine.Env.prototype.setInterval = jasmine.setInterval;
 37 jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
 38 
 39 /**
 40  * @returns an object containing jasmine version build info, if set.
 41  */
 42 jasmine.Env.prototype.version = function () {
 43   if (jasmine.version_) {
 44     return jasmine.version_;
 45   } else {
 46     throw new Error('Version not set');
 47   }
 48 };
 49 
 50 /**
 51  * @returns string containing jasmine version build info, if set.
 52  */
 53 jasmine.Env.prototype.versionString = function() {
 54   if (jasmine.version_) {
 55     var version = this.version();
 56     return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
 57   } else {
 58     return "version unknown";
 59   }
 60 };
 61 
 62 /**
 63  * @returns a sequential integer starting at 0
 64  */
 65 jasmine.Env.prototype.nextSpecId = function () {
 66   return this.nextSpecId_++;
 67 };
 68 
 69 /**
 70  * @returns a sequential integer starting at 0
 71  */
 72 jasmine.Env.prototype.nextSuiteId = function () {
 73   return this.nextSuiteId_++;
 74 };
 75 
 76 /**
 77  * Register a reporter to receive status updates from Jasmine.
 78  * @param {jasmine.Reporter} reporter An object which will receive status updates.
 79  */
 80 jasmine.Env.prototype.addReporter = function(reporter) {
 81   this.reporter.addReporter(reporter);
 82 };
 83 
 84 jasmine.Env.prototype.execute = function() {
 85   this.currentRunner_.execute();
 86 };
 87 
 88 jasmine.Env.prototype.describe = function(description, specDefinitions) {
 89   var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
 90 
 91   var parentSuite = this.currentSuite;
 92   if (parentSuite) {
 93     parentSuite.add(suite);
 94   } else {
 95     this.currentRunner_.add(suite);
 96   }
 97 
 98   this.currentSuite = suite;
 99 
100   var declarationError = null;
101   try {
102     specDefinitions.call(suite);
103   } catch(e) {
104     declarationError = e;
105   }
106 
107   this.currentSuite = parentSuite;
108 
109   if (declarationError) {
110     this.it("encountered a declaration exception", function() {
111       throw declarationError;
112     });
113   }
114 
115   return suite;
116 };
117 
118 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
119   if (this.currentSuite) {
120     this.currentSuite.beforeEach(beforeEachFunction);
121   } else {
122     this.currentRunner_.beforeEach(beforeEachFunction);
123   }
124 };
125 
126 jasmine.Env.prototype.currentRunner = function () {
127   return this.currentRunner_;
128 };
129 
130 jasmine.Env.prototype.afterEach = function(afterEachFunction) {
131   if (this.currentSuite) {
132     this.currentSuite.afterEach(afterEachFunction);
133   } else {
134     this.currentRunner_.afterEach(afterEachFunction);
135   }
136 
137 };
138 
139 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
140   return {
141     execute: function() {
142     }
143   };
144 };
145 
146 jasmine.Env.prototype.it = function(description, func) {
147   var spec = new jasmine.Spec(this, this.currentSuite, description);
148   this.currentSuite.add(spec);
149   this.currentSpec = spec;
150 
151   if (func) {
152     spec.runs(func);
153   }
154 
155   return spec;
156 };
157 
158 jasmine.Env.prototype.xit = function(desc, func) {
159   return {
160     id: this.nextSpecId(),
161     runs: function() {
162     }
163   };
164 };
165 
166 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
167   if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
168     return true;
169   }
170 
171   a.__Jasmine_been_here_before__ = b;
172   b.__Jasmine_been_here_before__ = a;
173 
174   var hasKey = function(obj, keyName) {
175     return obj != null && obj[keyName] !== jasmine.undefined;
176   };
177 
178   for (var property in b) {
179     if (!hasKey(a, property) && hasKey(b, property)) {
180       mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
181     }
182   }
183   for (property in a) {
184     if (!hasKey(b, property) && hasKey(a, property)) {
185       mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
186     }
187   }
188   for (property in b) {
189     if (property == '__Jasmine_been_here_before__') continue;
190     if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
191       mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
192     }
193   }
194 
195   if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
196     mismatchValues.push("arrays were not the same length");
197   }
198 
199   delete a.__Jasmine_been_here_before__;
200   delete b.__Jasmine_been_here_before__;
201   return (mismatchKeys.length == 0 && mismatchValues.length == 0);
202 };
203 
204 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
205   mismatchKeys = mismatchKeys || [];
206   mismatchValues = mismatchValues || [];
207 
208   for (var i = 0; i < this.equalityTesters_.length; i++) {
209     var equalityTester = this.equalityTesters_[i];
210     var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
211     if (result !== jasmine.undefined) return result;
212   }
213 
214   if (a === b) return true;
215 
216   if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
217     return (a == jasmine.undefined && b == jasmine.undefined);
218   }
219 
220   if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
221     return a === b;
222   }
223 
224   if (a instanceof Date && b instanceof Date) {
225     return a.getTime() == b.getTime();
226   }
227 
228   if (a instanceof jasmine.Matchers.Any) {
229     return a.matches(b);
230   }
231 
232   if (b instanceof jasmine.Matchers.Any) {
233     return b.matches(a);
234   }
235 
236   if (jasmine.isString_(a) && jasmine.isString_(b)) {
237     return (a == b);
238   }
239 
240   if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
241     return (a == b);
242   }
243 
244   if (typeof a === "object" && typeof b === "object") {
245     return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
246   }
247 
248   //Straight check
249   return (a === b);
250 };
251 
252 jasmine.Env.prototype.contains_ = function(haystack, needle) {
253   if (jasmine.isArray_(haystack)) {
254     for (var i = 0; i < haystack.length; i++) {
255       if (this.equals_(haystack[i], needle)) return true;
256     }
257     return false;
258   }
259   return haystack.indexOf(needle) >= 0;
260 };
261 
262 jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
263   this.equalityTesters_.push(equalityTester);
264 };
265