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