b943aaa7 by Michael Richards

Reorganize testing dependencies.

1 parent 005ac9e7
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 "http://www.w3.org/TR/html4/loose.dtd">
3 <html>
4 <head>
5 <title>Rivets test suite</title>
6
7 <link rel="stylesheet" type="text/css" href="lib/jasmine.css">
8
9 <script type="text/javascript" src="lib/jasmine.js"></script>
10 <script type="text/javascript" src="lib/jasmine-html.js"></script>
11
12 <script type="text/javascript" src="../lib/rivets.js"></script>
13
14 <script type="text/javascript" src="matchers.js"></script>
15 <script type="text/javascript" src="mock.data.js"></script>
16 <script type="text/javascript" src="rivets.js"></script>
17
18 <script type="text/javascript">
19 (function() {
20 var jasmineEnv = jasmine.getEnv();
21 jasmineEnv.updateInterval = 250;
22
23 var htmlReporter = new jasmine.HtmlReporter();
24
25 jasmineEnv.addReporter(htmlReporter);
26
27 jasmineEnv.specFilter = function(spec) {
28 return htmlReporter.specFilter(spec);
29 };
30
31 var currentWindowOnload = window.onload;
32
33 window.onload = function() {
34 if (currentWindowOnload) {
35 currentWindowOnload();
36 }
37 execJasmine();
38 };
39
40 function execJasmine() {
41 jasmineEnv.execute();
42 }
43
44 })();
45 </script>
46
47 </head>
48
49 <body>
50 </body>
51 </html>
1 Copyright (c) 2008-2011 Pivotal Labs
2
3 Permission is hereby granted, free of charge, to any person obtaining
4 a copy of this software and associated documentation files (the
5 "Software"), to deal in the Software without restriction, including
6 without limitation the rights to use, copy, modify, merge, publish,
7 distribute, sublicense, and/or sell copies of the Software, and to
8 permit persons to whom the Software is furnished to do so, subject to
9 the following conditions:
10
11 The above copyright notice and this permission notice shall be
12 included in all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 jasmine.HtmlReporterHelpers = {};
2
3 jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
4 var el = document.createElement(type);
5
6 for (var i = 2; i < arguments.length; i++) {
7 var child = arguments[i];
8
9 if (typeof child === 'string') {
10 el.appendChild(document.createTextNode(child));
11 } else {
12 if (child) {
13 el.appendChild(child);
14 }
15 }
16 }
17
18 for (var attr in attrs) {
19 if (attr == "className") {
20 el[attr] = attrs[attr];
21 } else {
22 el.setAttribute(attr, attrs[attr]);
23 }
24 }
25
26 return el;
27 };
28
29 jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
30 var results = child.results();
31 var status = results.passed() ? 'passed' : 'failed';
32 if (results.skipped) {
33 status = 'skipped';
34 }
35
36 return status;
37 };
38
39 jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
40 var parentDiv = this.dom.summary;
41 var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
42 var parent = child[parentSuite];
43
44 if (parent) {
45 if (typeof this.views.suites[parent.id] == 'undefined') {
46 this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
47 }
48 parentDiv = this.views.suites[parent.id].element;
49 }
50
51 parentDiv.appendChild(childElement);
52 };
53
54
55 jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
56 for(var fn in jasmine.HtmlReporterHelpers) {
57 ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
58 }
59 };
60
61 jasmine.HtmlReporter = function(_doc) {
62 var self = this;
63 var doc = _doc || window.document;
64
65 var reporterView;
66
67 var dom = {};
68
69 // Jasmine Reporter Public Interface
70 self.logRunningSpecs = false;
71
72 self.reportRunnerStarting = function(runner) {
73 var specs = runner.specs() || [];
74
75 if (specs.length == 0) {
76 return;
77 }
78
79 createReporterDom(runner.env.versionString());
80 doc.body.appendChild(dom.reporter);
81
82 reporterView = new jasmine.HtmlReporter.ReporterView(dom);
83 reporterView.addSpecs(specs, self.specFilter);
84 };
85
86 self.reportRunnerResults = function(runner) {
87 reporterView && reporterView.complete();
88 };
89
90 self.reportSuiteResults = function(suite) {
91 reporterView.suiteComplete(suite);
92 };
93
94 self.reportSpecStarting = function(spec) {
95 if (self.logRunningSpecs) {
96 self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
97 }
98 };
99
100 self.reportSpecResults = function(spec) {
101 reporterView.specComplete(spec);
102 };
103
104 self.log = function() {
105 var console = jasmine.getGlobal().console;
106 if (console && console.log) {
107 if (console.log.apply) {
108 console.log.apply(console, arguments);
109 } else {
110 console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
111 }
112 }
113 };
114
115 self.specFilter = function(spec) {
116 if (!focusedSpecName()) {
117 return true;
118 }
119
120 return spec.getFullName().indexOf(focusedSpecName()) === 0;
121 };
122
123 return self;
124
125 function focusedSpecName() {
126 var specName;
127
128 (function memoizeFocusedSpec() {
129 if (specName) {
130 return;
131 }
132
133 var paramMap = [];
134 var params = doc.location.search.substring(1).split('&');
135
136 for (var i = 0; i < params.length; i++) {
137 var p = params[i].split('=');
138 paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
139 }
140
141 specName = paramMap.spec;
142 })();
143
144 return specName;
145 }
146
147 function createReporterDom(version) {
148 dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
149 dom.banner = self.createDom('div', { className: 'banner' },
150 self.createDom('span', { className: 'title' }, "Jasmine "),
151 self.createDom('span', { className: 'version' }, version)),
152
153 dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
154 dom.alert = self.createDom('div', {className: 'alert'}),
155 dom.results = self.createDom('div', {className: 'results'},
156 dom.summary = self.createDom('div', { className: 'summary' }),
157 dom.details = self.createDom('div', { id: 'details' }))
158 );
159 }
160 };
161 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
162 this.startedAt = new Date();
163 this.runningSpecCount = 0;
164 this.completeSpecCount = 0;
165 this.passedCount = 0;
166 this.failedCount = 0;
167 this.skippedCount = 0;
168
169 this.createResultsMenu = function() {
170 this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
171 this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
172 ' | ',
173 this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
174
175 this.summaryMenuItem.onclick = function() {
176 dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
177 };
178
179 this.detailsMenuItem.onclick = function() {
180 showDetails();
181 };
182 };
183
184 this.addSpecs = function(specs, specFilter) {
185 this.totalSpecCount = specs.length;
186
187 this.views = {
188 specs: {},
189 suites: {}
190 };
191
192 for (var i = 0; i < specs.length; i++) {
193 var spec = specs[i];
194 this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
195 if (specFilter(spec)) {
196 this.runningSpecCount++;
197 }
198 }
199 };
200
201 this.specComplete = function(spec) {
202 this.completeSpecCount++;
203
204 if (isUndefined(this.views.specs[spec.id])) {
205 this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
206 }
207
208 var specView = this.views.specs[spec.id];
209
210 switch (specView.status()) {
211 case 'passed':
212 this.passedCount++;
213 break;
214
215 case 'failed':
216 this.failedCount++;
217 break;
218
219 case 'skipped':
220 this.skippedCount++;
221 break;
222 }
223
224 specView.refresh();
225 this.refresh();
226 };
227
228 this.suiteComplete = function(suite) {
229 var suiteView = this.views.suites[suite.id];
230 if (isUndefined(suiteView)) {
231 return;
232 }
233 suiteView.refresh();
234 };
235
236 this.refresh = function() {
237
238 if (isUndefined(this.resultsMenu)) {
239 this.createResultsMenu();
240 }
241
242 // currently running UI
243 if (isUndefined(this.runningAlert)) {
244 this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
245 dom.alert.appendChild(this.runningAlert);
246 }
247 this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
248
249 // skipped specs UI
250 if (isUndefined(this.skippedAlert)) {
251 this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
252 }
253
254 this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
255
256 if (this.skippedCount === 1 && isDefined(dom.alert)) {
257 dom.alert.appendChild(this.skippedAlert);
258 }
259
260 // passing specs UI
261 if (isUndefined(this.passedAlert)) {
262 this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
263 }
264 this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
265
266 // failing specs UI
267 if (isUndefined(this.failedAlert)) {
268 this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
269 }
270 this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
271
272 if (this.failedCount === 1 && isDefined(dom.alert)) {
273 dom.alert.appendChild(this.failedAlert);
274 dom.alert.appendChild(this.resultsMenu);
275 }
276
277 // summary info
278 this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
279 this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
280 };
281
282 this.complete = function() {
283 dom.alert.removeChild(this.runningAlert);
284
285 this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
286
287 if (this.failedCount === 0) {
288 dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
289 } else {
290 showDetails();
291 }
292
293 dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
294 };
295
296 return this;
297
298 function showDetails() {
299 if (dom.reporter.className.search(/showDetails/) === -1) {
300 dom.reporter.className += " showDetails";
301 }
302 }
303
304 function isUndefined(obj) {
305 return typeof obj === 'undefined';
306 }
307
308 function isDefined(obj) {
309 return !isUndefined(obj);
310 }
311
312 function specPluralizedFor(count) {
313 var str = count + " spec";
314 if (count > 1) {
315 str += "s"
316 }
317 return str;
318 }
319
320 };
321
322 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
323
324
325 jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
326 this.spec = spec;
327 this.dom = dom;
328 this.views = views;
329
330 this.symbol = this.createDom('li', { className: 'pending' });
331 this.dom.symbolSummary.appendChild(this.symbol);
332
333 this.summary = this.createDom('div', { className: 'specSummary' },
334 this.createDom('a', {
335 className: 'description',
336 href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
337 title: this.spec.getFullName()
338 }, this.spec.description)
339 );
340
341 this.detail = this.createDom('div', { className: 'specDetail' },
342 this.createDom('a', {
343 className: 'description',
344 href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
345 title: this.spec.getFullName()
346 }, this.spec.getFullName())
347 );
348 };
349
350 jasmine.HtmlReporter.SpecView.prototype.status = function() {
351 return this.getSpecStatus(this.spec);
352 };
353
354 jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
355 this.symbol.className = this.status();
356
357 switch (this.status()) {
358 case 'skipped':
359 break;
360
361 case 'passed':
362 this.appendSummaryToSuiteDiv();
363 break;
364
365 case 'failed':
366 this.appendSummaryToSuiteDiv();
367 this.appendFailureDetail();
368 break;
369 }
370 };
371
372 jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
373 this.summary.className += ' ' + this.status();
374 this.appendToSummary(this.spec, this.summary);
375 };
376
377 jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
378 this.detail.className += ' ' + this.status();
379
380 var resultItems = this.spec.results().getItems();
381 var messagesDiv = this.createDom('div', { className: 'messages' });
382
383 for (var i = 0; i < resultItems.length; i++) {
384 var result = resultItems[i];
385
386 if (result.type == 'log') {
387 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
388 } else if (result.type == 'expect' && result.passed && !result.passed()) {
389 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
390
391 if (result.trace.stack) {
392 messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
393 }
394 }
395 }
396
397 if (messagesDiv.childNodes.length > 0) {
398 this.detail.appendChild(messagesDiv);
399 this.dom.details.appendChild(this.detail);
400 }
401 };
402
403 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
404 this.suite = suite;
405 this.dom = dom;
406 this.views = views;
407
408 this.element = this.createDom('div', { className: 'suite' },
409 this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
410 );
411
412 this.appendToSummary(this.suite, this.element);
413 };
414
415 jasmine.HtmlReporter.SuiteView.prototype.status = function() {
416 return this.getSpecStatus(this.suite);
417 };
418
419 jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
420 this.element.className += " " + this.status();
421 };
422
423 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
424
425 /* @deprecated Use jasmine.HtmlReporter instead
426 */
427 jasmine.TrivialReporter = function(doc) {
428 this.document = doc || document;
429 this.suiteDivs = {};
430 this.logRunningSpecs = false;
431 };
432
433 jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
434 var el = document.createElement(type);
435
436 for (var i = 2; i < arguments.length; i++) {
437 var child = arguments[i];
438
439 if (typeof child === 'string') {
440 el.appendChild(document.createTextNode(child));
441 } else {
442 if (child) { el.appendChild(child); }
443 }
444 }
445
446 for (var attr in attrs) {
447 if (attr == "className") {
448 el[attr] = attrs[attr];
449 } else {
450 el.setAttribute(attr, attrs[attr]);
451 }
452 }
453
454 return el;
455 };
456
457 jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
458 var showPassed, showSkipped;
459
460 this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
461 this.createDom('div', { className: 'banner' },
462 this.createDom('div', { className: 'logo' },
463 this.createDom('span', { className: 'title' }, "Jasmine"),
464 this.createDom('span', { className: 'version' }, runner.env.versionString())),
465 this.createDom('div', { className: 'options' },
466 "Show ",
467 showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
468 this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
469 showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
470 this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
471 )
472 ),
473
474 this.runnerDiv = this.createDom('div', { className: 'runner running' },
475 this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
476 this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
477 this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
478 );
479
480 this.document.body.appendChild(this.outerDiv);
481
482 var suites = runner.suites();
483 for (var i = 0; i < suites.length; i++) {
484 var suite = suites[i];
485 var suiteDiv = this.createDom('div', { className: 'suite' },
486 this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
487 this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
488 this.suiteDivs[suite.id] = suiteDiv;
489 var parentDiv = this.outerDiv;
490 if (suite.parentSuite) {
491 parentDiv = this.suiteDivs[suite.parentSuite.id];
492 }
493 parentDiv.appendChild(suiteDiv);
494 }
495
496 this.startedAt = new Date();
497
498 var self = this;
499 showPassed.onclick = function(evt) {
500 if (showPassed.checked) {
501 self.outerDiv.className += ' show-passed';
502 } else {
503 self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
504 }
505 };
506
507 showSkipped.onclick = function(evt) {
508 if (showSkipped.checked) {
509 self.outerDiv.className += ' show-skipped';
510 } else {
511 self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
512 }
513 };
514 };
515
516 jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
517 var results = runner.results();
518 var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
519 this.runnerDiv.setAttribute("class", className);
520 //do it twice for IE
521 this.runnerDiv.setAttribute("className", className);
522 var specs = runner.specs();
523 var specCount = 0;
524 for (var i = 0; i < specs.length; i++) {
525 if (this.specFilter(specs[i])) {
526 specCount++;
527 }
528 }
529 var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
530 message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
531 this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
532
533 this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
534 };
535
536 jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
537 var results = suite.results();
538 var status = results.passed() ? 'passed' : 'failed';
539 if (results.totalCount === 0) { // todo: change this to check results.skipped
540 status = 'skipped';
541 }
542 this.suiteDivs[suite.id].className += " " + status;
543 };
544
545 jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
546 if (this.logRunningSpecs) {
547 this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
548 }
549 };
550
551 jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
552 var results = spec.results();
553 var status = results.passed() ? 'passed' : 'failed';
554 if (results.skipped) {
555 status = 'skipped';
556 }
557 var specDiv = this.createDom('div', { className: 'spec ' + status },
558 this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
559 this.createDom('a', {
560 className: 'description',
561 href: '?spec=' + encodeURIComponent(spec.getFullName()),
562 title: spec.getFullName()
563 }, spec.description));
564
565
566 var resultItems = results.getItems();
567 var messagesDiv = this.createDom('div', { className: 'messages' });
568 for (var i = 0; i < resultItems.length; i++) {
569 var result = resultItems[i];
570
571 if (result.type == 'log') {
572 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
573 } else if (result.type == 'expect' && result.passed && !result.passed()) {
574 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
575
576 if (result.trace.stack) {
577 messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
578 }
579 }
580 }
581
582 if (messagesDiv.childNodes.length > 0) {
583 specDiv.appendChild(messagesDiv);
584 }
585
586 this.suiteDivs[spec.suite.id].appendChild(specDiv);
587 };
588
589 jasmine.TrivialReporter.prototype.log = function() {
590 var console = jasmine.getGlobal().console;
591 if (console && console.log) {
592 if (console.log.apply) {
593 console.log.apply(console, arguments);
594 } else {
595 console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
596 }
597 }
598 };
599
600 jasmine.TrivialReporter.prototype.getLocation = function() {
601 return this.document.location;
602 };
603
604 jasmine.TrivialReporter.prototype.specFilter = function(spec) {
605 var paramMap = {};
606 var params = this.getLocation().search.substring(1).split('&');
607 for (var i = 0; i < params.length; i++) {
608 var p = params[i].split('=');
609 paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
610 }
611
612 if (!paramMap.spec) {
613 return true;
614 }
615 return spec.getFullName().indexOf(paramMap.spec) === 0;
616 };
1 body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2
3 #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 #HTMLReporter a { text-decoration: none; }
5 #HTMLReporter a:hover { text-decoration: underline; }
6 #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 #HTMLReporter .version { color: #aaaaaa; }
10 #HTMLReporter .banner { margin-top: 14px; }
11 #HTMLReporter .duration { color: #aaaaaa; float: right; }
12 #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
23 #HTMLReporter .runningAlert { background-color: #666666; }
24 #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
25 #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
26 #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
27 #HTMLReporter .passingAlert { background-color: #a6b779; }
28 #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
29 #HTMLReporter .failingAlert { background-color: #cf867e; }
30 #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
31 #HTMLReporter .results { margin-top: 14px; }
32 #HTMLReporter #details { display: none; }
33 #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
34 #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
35 #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
36 #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
37 #HTMLReporter.showDetails .summary { display: none; }
38 #HTMLReporter.showDetails #details { display: block; }
39 #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
40 #HTMLReporter .summary { margin-top: 14px; }
41 #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
42 #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
43 #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
44 #HTMLReporter .description + .suite { margin-top: 0; }
45 #HTMLReporter .suite { margin-top: 14px; }
46 #HTMLReporter .suite a { color: #333333; }
47 #HTMLReporter #details .specDetail { margin-bottom: 28px; }
48 #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
49 #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
50 #HTMLReporter .resultMessage span.result { display: block; }
51 #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
52
53 #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
54 #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
55 #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
56 #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
57 #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
58 #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
59 #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
60 #TrivialReporter .runner.running { background-color: yellow; }
61 #TrivialReporter .options { text-align: right; font-size: .8em; }
62 #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
63 #TrivialReporter .suite .suite { margin: 5px; }
64 #TrivialReporter .suite.passed { background-color: #dfd; }
65 #TrivialReporter .suite.failed { background-color: #fdd; }
66 #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
67 #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
68 #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
69 #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
70 #TrivialReporter .spec.skipped { background-color: #bbb; }
71 #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
72 #TrivialReporter .passed { background-color: #cfc; display: none; }
73 #TrivialReporter .failed { background-color: #fbb; }
74 #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
75 #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
76 #TrivialReporter .resultMessage .mismatch { color: black; }
77 #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
78 #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
79 #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
80 #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
81 #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
1 var isCommonJS = typeof window == "undefined";
2
3 /**
4 * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
5 *
6 * @namespace
7 */
8 var jasmine = {};
9 if (isCommonJS) exports.jasmine = jasmine;
10 /**
11 * @private
12 */
13 jasmine.unimplementedMethod_ = function() {
14 throw new Error("unimplemented method");
15 };
16
17 /**
18 * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
19 * a plain old variable and may be redefined by somebody else.
20 *
21 * @private
22 */
23 jasmine.undefined = jasmine.___undefined___;
24
25 /**
26 * Show diagnostic messages in the console if set to true
27 *
28 */
29 jasmine.VERBOSE = false;
30
31 /**
32 * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
33 *
34 */
35 jasmine.DEFAULT_UPDATE_INTERVAL = 250;
36
37 /**
38 * Default timeout interval in milliseconds for waitsFor() blocks.
39 */
40 jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
41
42 jasmine.getGlobal = function() {
43 function getGlobal() {
44 return this;
45 }
46
47 return getGlobal();
48 };
49
50 /**
51 * Allows for bound functions to be compared. Internal use only.
52 *
53 * @ignore
54 * @private
55 * @param base {Object} bound 'this' for the function
56 * @param name {Function} function to find
57 */
58 jasmine.bindOriginal_ = function(base, name) {
59 var original = base[name];
60 if (original.apply) {
61 return function() {
62 return original.apply(base, arguments);
63 };
64 } else {
65 // IE support
66 return jasmine.getGlobal()[name];
67 }
68 };
69
70 jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
71 jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
72 jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
73 jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
74
75 jasmine.MessageResult = function(values) {
76 this.type = 'log';
77 this.values = values;
78 this.trace = new Error(); // todo: test better
79 };
80
81 jasmine.MessageResult.prototype.toString = function() {
82 var text = "";
83 for (var i = 0; i < this.values.length; i++) {
84 if (i > 0) text += " ";
85 if (jasmine.isString_(this.values[i])) {
86 text += this.values[i];
87 } else {
88 text += jasmine.pp(this.values[i]);
89 }
90 }
91 return text;
92 };
93
94 jasmine.ExpectationResult = function(params) {
95 this.type = 'expect';
96 this.matcherName = params.matcherName;
97 this.passed_ = params.passed;
98 this.expected = params.expected;
99 this.actual = params.actual;
100 this.message = this.passed_ ? 'Passed.' : params.message;
101
102 var trace = (params.trace || new Error(this.message));
103 this.trace = this.passed_ ? '' : trace;
104 };
105
106 jasmine.ExpectationResult.prototype.toString = function () {
107 return this.message;
108 };
109
110 jasmine.ExpectationResult.prototype.passed = function () {
111 return this.passed_;
112 };
113
114 /**
115 * Getter for the Jasmine environment. Ensures one gets created
116 */
117 jasmine.getEnv = function() {
118 var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
119 return env;
120 };
121
122 /**
123 * @ignore
124 * @private
125 * @param value
126 * @returns {Boolean}
127 */
128 jasmine.isArray_ = function(value) {
129 return jasmine.isA_("Array", value);
130 };
131
132 /**
133 * @ignore
134 * @private
135 * @param value
136 * @returns {Boolean}
137 */
138 jasmine.isString_ = function(value) {
139 return jasmine.isA_("String", value);
140 };
141
142 /**
143 * @ignore
144 * @private
145 * @param value
146 * @returns {Boolean}
147 */
148 jasmine.isNumber_ = function(value) {
149 return jasmine.isA_("Number", value);
150 };
151
152 /**
153 * @ignore
154 * @private
155 * @param {String} typeName
156 * @param value
157 * @returns {Boolean}
158 */
159 jasmine.isA_ = function(typeName, value) {
160 return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
161 };
162
163 /**
164 * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
165 *
166 * @param value {Object} an object to be outputted
167 * @returns {String}
168 */
169 jasmine.pp = function(value) {
170 var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
171 stringPrettyPrinter.format(value);
172 return stringPrettyPrinter.string;
173 };
174
175 /**
176 * Returns true if the object is a DOM Node.
177 *
178 * @param {Object} obj object to check
179 * @returns {Boolean}
180 */
181 jasmine.isDomNode = function(obj) {
182 return obj.nodeType > 0;
183 };
184
185 /**
186 * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
187 *
188 * @example
189 * // don't care about which function is passed in, as long as it's a function
190 * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
191 *
192 * @param {Class} clazz
193 * @returns matchable object of the type clazz
194 */
195 jasmine.any = function(clazz) {
196 return new jasmine.Matchers.Any(clazz);
197 };
198
199 /**
200 * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
201 * attributes on the object.
202 *
203 * @example
204 * // don't care about any other attributes than foo.
205 * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
206 *
207 * @param sample {Object} sample
208 * @returns matchable object for the sample
209 */
210 jasmine.objectContaining = function (sample) {
211 return new jasmine.Matchers.ObjectContaining(sample);
212 };
213
214 /**
215 * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
216 *
217 * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
218 * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
219 *
220 * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
221 *
222 * Spies are torn down at the end of every spec.
223 *
224 * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
225 *
226 * @example
227 * // a stub
228 * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
229 *
230 * // spy example
231 * var foo = {
232 * not: function(bool) { return !bool; }
233 * }
234 *
235 * // actual foo.not will not be called, execution stops
236 * spyOn(foo, 'not');
237
238 // foo.not spied upon, execution will continue to implementation
239 * spyOn(foo, 'not').andCallThrough();
240 *
241 * // fake example
242 * var foo = {
243 * not: function(bool) { return !bool; }
244 * }
245 *
246 * // foo.not(val) will return val
247 * spyOn(foo, 'not').andCallFake(function(value) {return value;});
248 *
249 * // mock example
250 * foo.not(7 == 7);
251 * expect(foo.not).toHaveBeenCalled();
252 * expect(foo.not).toHaveBeenCalledWith(true);
253 *
254 * @constructor
255 * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
256 * @param {String} name
257 */
258 jasmine.Spy = function(name) {
259 /**
260 * The name of the spy, if provided.
261 */
262 this.identity = name || 'unknown';
263 /**
264 * Is this Object a spy?
265 */
266 this.isSpy = true;
267 /**
268 * The actual function this spy stubs.
269 */
270 this.plan = function() {
271 };
272 /**
273 * Tracking of the most recent call to the spy.
274 * @example
275 * var mySpy = jasmine.createSpy('foo');
276 * mySpy(1, 2);
277 * mySpy.mostRecentCall.args = [1, 2];
278 */
279 this.mostRecentCall = {};
280
281 /**
282 * Holds arguments for each call to the spy, indexed by call count
283 * @example
284 * var mySpy = jasmine.createSpy('foo');
285 * mySpy(1, 2);
286 * mySpy(7, 8);
287 * mySpy.mostRecentCall.args = [7, 8];
288 * mySpy.argsForCall[0] = [1, 2];
289 * mySpy.argsForCall[1] = [7, 8];
290 */
291 this.argsForCall = [];
292 this.calls = [];
293 };
294
295 /**
296 * Tells a spy to call through to the actual implemenatation.
297 *
298 * @example
299 * var foo = {
300 * bar: function() { // do some stuff }
301 * }
302 *
303 * // defining a spy on an existing property: foo.bar
304 * spyOn(foo, 'bar').andCallThrough();
305 */
306 jasmine.Spy.prototype.andCallThrough = function() {
307 this.plan = this.originalValue;
308 return this;
309 };
310
311 /**
312 * For setting the return value of a spy.
313 *
314 * @example
315 * // defining a spy from scratch: foo() returns 'baz'
316 * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
317 *
318 * // defining a spy on an existing property: foo.bar() returns 'baz'
319 * spyOn(foo, 'bar').andReturn('baz');
320 *
321 * @param {Object} value
322 */
323 jasmine.Spy.prototype.andReturn = function(value) {
324 this.plan = function() {
325 return value;
326 };
327 return this;
328 };
329
330 /**
331 * For throwing an exception when a spy is called.
332 *
333 * @example
334 * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
335 * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
336 *
337 * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
338 * spyOn(foo, 'bar').andThrow('baz');
339 *
340 * @param {String} exceptionMsg
341 */
342 jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
343 this.plan = function() {
344 throw exceptionMsg;
345 };
346 return this;
347 };
348
349 /**
350 * Calls an alternate implementation when a spy is called.
351 *
352 * @example
353 * var baz = function() {
354 * // do some stuff, return something
355 * }
356 * // defining a spy from scratch: foo() calls the function baz
357 * var foo = jasmine.createSpy('spy on foo').andCall(baz);
358 *
359 * // defining a spy on an existing property: foo.bar() calls an anonymnous function
360 * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
361 *
362 * @param {Function} fakeFunc
363 */
364 jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
365 this.plan = fakeFunc;
366 return this;
367 };
368
369 /**
370 * Resets all of a spy's the tracking variables so that it can be used again.
371 *
372 * @example
373 * spyOn(foo, 'bar');
374 *
375 * foo.bar();
376 *
377 * expect(foo.bar.callCount).toEqual(1);
378 *
379 * foo.bar.reset();
380 *
381 * expect(foo.bar.callCount).toEqual(0);
382 */
383 jasmine.Spy.prototype.reset = function() {
384 this.wasCalled = false;
385 this.callCount = 0;
386 this.argsForCall = [];
387 this.calls = [];
388 this.mostRecentCall = {};
389 };
390
391 jasmine.createSpy = function(name) {
392
393 var spyObj = function() {
394 spyObj.wasCalled = true;
395 spyObj.callCount++;
396 var args = jasmine.util.argsToArray(arguments);
397 spyObj.mostRecentCall.object = this;
398 spyObj.mostRecentCall.args = args;
399 spyObj.argsForCall.push(args);
400 spyObj.calls.push({object: this, args: args});
401 return spyObj.plan.apply(this, arguments);
402 };
403
404 var spy = new jasmine.Spy(name);
405
406 for (var prop in spy) {
407 spyObj[prop] = spy[prop];
408 }
409
410 spyObj.reset();
411
412 return spyObj;
413 };
414
415 /**
416 * Determines whether an object is a spy.
417 *
418 * @param {jasmine.Spy|Object} putativeSpy
419 * @returns {Boolean}
420 */
421 jasmine.isSpy = function(putativeSpy) {
422 return putativeSpy && putativeSpy.isSpy;
423 };
424
425 /**
426 * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
427 * large in one call.
428 *
429 * @param {String} baseName name of spy class
430 * @param {Array} methodNames array of names of methods to make spies
431 */
432 jasmine.createSpyObj = function(baseName, methodNames) {
433 if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
434 throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
435 }
436 var obj = {};
437 for (var i = 0; i < methodNames.length; i++) {
438 obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
439 }
440 return obj;
441 };
442
443 /**
444 * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
445 *
446 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
447 */
448 jasmine.log = function() {
449 var spec = jasmine.getEnv().currentSpec;
450 spec.log.apply(spec, arguments);
451 };
452
453 /**
454 * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
455 *
456 * @example
457 * // spy example
458 * var foo = {
459 * not: function(bool) { return !bool; }
460 * }
461 * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
462 *
463 * @see jasmine.createSpy
464 * @param obj
465 * @param methodName
466 * @returns a Jasmine spy that can be chained with all spy methods
467 */
468 var spyOn = function(obj, methodName) {
469 return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
470 };
471 if (isCommonJS) exports.spyOn = spyOn;
472
473 /**
474 * Creates a Jasmine spec that will be added to the current suite.
475 *
476 * // TODO: pending tests
477 *
478 * @example
479 * it('should be true', function() {
480 * expect(true).toEqual(true);
481 * });
482 *
483 * @param {String} desc description of this specification
484 * @param {Function} func defines the preconditions and expectations of the spec
485 */
486 var it = function(desc, func) {
487 return jasmine.getEnv().it(desc, func);
488 };
489 if (isCommonJS) exports.it = it;
490
491 /**
492 * Creates a <em>disabled</em> Jasmine spec.
493 *
494 * A convenience method that allows existing specs to be disabled temporarily during development.
495 *
496 * @param {String} desc description of this specification
497 * @param {Function} func defines the preconditions and expectations of the spec
498 */
499 var xit = function(desc, func) {
500 return jasmine.getEnv().xit(desc, func);
501 };
502 if (isCommonJS) exports.xit = xit;
503
504 /**
505 * Starts a chain for a Jasmine expectation.
506 *
507 * It is passed an Object that is the actual value and should chain to one of the many
508 * jasmine.Matchers functions.
509 *
510 * @param {Object} actual Actual value to test against and expected value
511 */
512 var expect = function(actual) {
513 return jasmine.getEnv().currentSpec.expect(actual);
514 };
515 if (isCommonJS) exports.expect = expect;
516
517 /**
518 * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
519 *
520 * @param {Function} func Function that defines part of a jasmine spec.
521 */
522 var runs = function(func) {
523 jasmine.getEnv().currentSpec.runs(func);
524 };
525 if (isCommonJS) exports.runs = runs;
526
527 /**
528 * Waits a fixed time period before moving to the next block.
529 *
530 * @deprecated Use waitsFor() instead
531 * @param {Number} timeout milliseconds to wait
532 */
533 var waits = function(timeout) {
534 jasmine.getEnv().currentSpec.waits(timeout);
535 };
536 if (isCommonJS) exports.waits = waits;
537
538 /**
539 * Waits for the latchFunction to return true before proceeding to the next block.
540 *
541 * @param {Function} latchFunction
542 * @param {String} optional_timeoutMessage
543 * @param {Number} optional_timeout
544 */
545 var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
546 jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
547 };
548 if (isCommonJS) exports.waitsFor = waitsFor;
549
550 /**
551 * A function that is called before each spec in a suite.
552 *
553 * Used for spec setup, including validating assumptions.
554 *
555 * @param {Function} beforeEachFunction
556 */
557 var beforeEach = function(beforeEachFunction) {
558 jasmine.getEnv().beforeEach(beforeEachFunction);
559 };
560 if (isCommonJS) exports.beforeEach = beforeEach;
561
562 /**
563 * A function that is called after each spec in a suite.
564 *
565 * Used for restoring any state that is hijacked during spec execution.
566 *
567 * @param {Function} afterEachFunction
568 */
569 var afterEach = function(afterEachFunction) {
570 jasmine.getEnv().afterEach(afterEachFunction);
571 };
572 if (isCommonJS) exports.afterEach = afterEach;
573
574 /**
575 * Defines a suite of specifications.
576 *
577 * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
578 * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
579 * of setup in some tests.
580 *
581 * @example
582 * // TODO: a simple suite
583 *
584 * // TODO: a simple suite with a nested describe block
585 *
586 * @param {String} description A string, usually the class under test.
587 * @param {Function} specDefinitions function that defines several specs.
588 */
589 var describe = function(description, specDefinitions) {
590 return jasmine.getEnv().describe(description, specDefinitions);
591 };
592 if (isCommonJS) exports.describe = describe;
593
594 /**
595 * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
596 *
597 * @param {String} description A string, usually the class under test.
598 * @param {Function} specDefinitions function that defines several specs.
599 */
600 var xdescribe = function(description, specDefinitions) {
601 return jasmine.getEnv().xdescribe(description, specDefinitions);
602 };
603 if (isCommonJS) exports.xdescribe = xdescribe;
604
605
606 // Provide the XMLHttpRequest class for IE 5.x-6.x:
607 jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
608 function tryIt(f) {
609 try {
610 return f();
611 } catch(e) {
612 }
613 return null;
614 }
615
616 var xhr = tryIt(function() {
617 return new ActiveXObject("Msxml2.XMLHTTP.6.0");
618 }) ||
619 tryIt(function() {
620 return new ActiveXObject("Msxml2.XMLHTTP.3.0");
621 }) ||
622 tryIt(function() {
623 return new ActiveXObject("Msxml2.XMLHTTP");
624 }) ||
625 tryIt(function() {
626 return new ActiveXObject("Microsoft.XMLHTTP");
627 });
628
629 if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
630
631 return xhr;
632 } : XMLHttpRequest;
633 /**
634 * @namespace
635 */
636 jasmine.util = {};
637
638 /**
639 * Declare that a child class inherit it's prototype from the parent class.
640 *
641 * @private
642 * @param {Function} childClass
643 * @param {Function} parentClass
644 */
645 jasmine.util.inherit = function(childClass, parentClass) {
646 /**
647 * @private
648 */
649 var subclass = function() {
650 };
651 subclass.prototype = parentClass.prototype;
652 childClass.prototype = new subclass();
653 };
654
655 jasmine.util.formatException = function(e) {
656 var lineNumber;
657 if (e.line) {
658 lineNumber = e.line;
659 }
660 else if (e.lineNumber) {
661 lineNumber = e.lineNumber;
662 }
663
664 var file;
665
666 if (e.sourceURL) {
667 file = e.sourceURL;
668 }
669 else if (e.fileName) {
670 file = e.fileName;
671 }
672
673 var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
674
675 if (file && lineNumber) {
676 message += ' in ' + file + ' (line ' + lineNumber + ')';
677 }
678
679 return message;
680 };
681
682 jasmine.util.htmlEscape = function(str) {
683 if (!str) return str;
684 return str.replace(/&/g, '&amp;')
685 .replace(/</g, '&lt;')
686 .replace(/>/g, '&gt;');
687 };
688
689 jasmine.util.argsToArray = function(args) {
690 var arrayOfArgs = [];
691 for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
692 return arrayOfArgs;
693 };
694
695 jasmine.util.extend = function(destination, source) {
696 for (var property in source) destination[property] = source[property];
697 return destination;
698 };
699
700 /**
701 * Environment for Jasmine
702 *
703 * @constructor
704 */
705 jasmine.Env = function() {
706 this.currentSpec = null;
707 this.currentSuite = null;
708 this.currentRunner_ = new jasmine.Runner(this);
709
710 this.reporter = new jasmine.MultiReporter();
711
712 this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
713 this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
714 this.lastUpdate = 0;
715 this.specFilter = function() {
716 return true;
717 };
718
719 this.nextSpecId_ = 0;
720 this.nextSuiteId_ = 0;
721 this.equalityTesters_ = [];
722
723 // wrap matchers
724 this.matchersClass = function() {
725 jasmine.Matchers.apply(this, arguments);
726 };
727 jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
728
729 jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
730 };
731
732
733 jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
734 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
735 jasmine.Env.prototype.setInterval = jasmine.setInterval;
736 jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
737
738 /**
739 * @returns an object containing jasmine version build info, if set.
740 */
741 jasmine.Env.prototype.version = function () {
742 if (jasmine.version_) {
743 return jasmine.version_;
744 } else {
745 throw new Error('Version not set');
746 }
747 };
748
749 /**
750 * @returns string containing jasmine version build info, if set.
751 */
752 jasmine.Env.prototype.versionString = function() {
753 if (!jasmine.version_) {
754 return "version unknown";
755 }
756
757 var version = this.version();
758 var versionString = version.major + "." + version.minor + "." + version.build;
759 if (version.release_candidate) {
760 versionString += ".rc" + version.release_candidate;
761 }
762 versionString += " revision " + version.revision;
763 return versionString;
764 };
765
766 /**
767 * @returns a sequential integer starting at 0
768 */
769 jasmine.Env.prototype.nextSpecId = function () {
770 return this.nextSpecId_++;
771 };
772
773 /**
774 * @returns a sequential integer starting at 0
775 */
776 jasmine.Env.prototype.nextSuiteId = function () {
777 return this.nextSuiteId_++;
778 };
779
780 /**
781 * Register a reporter to receive status updates from Jasmine.
782 * @param {jasmine.Reporter} reporter An object which will receive status updates.
783 */
784 jasmine.Env.prototype.addReporter = function(reporter) {
785 this.reporter.addReporter(reporter);
786 };
787
788 jasmine.Env.prototype.execute = function() {
789 this.currentRunner_.execute();
790 };
791
792 jasmine.Env.prototype.describe = function(description, specDefinitions) {
793 var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
794
795 var parentSuite = this.currentSuite;
796 if (parentSuite) {
797 parentSuite.add(suite);
798 } else {
799 this.currentRunner_.add(suite);
800 }
801
802 this.currentSuite = suite;
803
804 var declarationError = null;
805 try {
806 specDefinitions.call(suite);
807 } catch(e) {
808 declarationError = e;
809 }
810
811 if (declarationError) {
812 this.it("encountered a declaration exception", function() {
813 throw declarationError;
814 });
815 }
816
817 this.currentSuite = parentSuite;
818
819 return suite;
820 };
821
822 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
823 if (this.currentSuite) {
824 this.currentSuite.beforeEach(beforeEachFunction);
825 } else {
826 this.currentRunner_.beforeEach(beforeEachFunction);
827 }
828 };
829
830 jasmine.Env.prototype.currentRunner = function () {
831 return this.currentRunner_;
832 };
833
834 jasmine.Env.prototype.afterEach = function(afterEachFunction) {
835 if (this.currentSuite) {
836 this.currentSuite.afterEach(afterEachFunction);
837 } else {
838 this.currentRunner_.afterEach(afterEachFunction);
839 }
840
841 };
842
843 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
844 return {
845 execute: function() {
846 }
847 };
848 };
849
850 jasmine.Env.prototype.it = function(description, func) {
851 var spec = new jasmine.Spec(this, this.currentSuite, description);
852 this.currentSuite.add(spec);
853 this.currentSpec = spec;
854
855 if (func) {
856 spec.runs(func);
857 }
858
859 return spec;
860 };
861
862 jasmine.Env.prototype.xit = function(desc, func) {
863 return {
864 id: this.nextSpecId(),
865 runs: function() {
866 }
867 };
868 };
869
870 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
871 if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
872 return true;
873 }
874
875 a.__Jasmine_been_here_before__ = b;
876 b.__Jasmine_been_here_before__ = a;
877
878 var hasKey = function(obj, keyName) {
879 return obj !== null && obj[keyName] !== jasmine.undefined;
880 };
881
882 for (var property in b) {
883 if (!hasKey(a, property) && hasKey(b, property)) {
884 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
885 }
886 }
887 for (property in a) {
888 if (!hasKey(b, property) && hasKey(a, property)) {
889 mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
890 }
891 }
892 for (property in b) {
893 if (property == '__Jasmine_been_here_before__') continue;
894 if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
895 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.");
896 }
897 }
898
899 if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
900 mismatchValues.push("arrays were not the same length");
901 }
902
903 delete a.__Jasmine_been_here_before__;
904 delete b.__Jasmine_been_here_before__;
905 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
906 };
907
908 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
909 mismatchKeys = mismatchKeys || [];
910 mismatchValues = mismatchValues || [];
911
912 for (var i = 0; i < this.equalityTesters_.length; i++) {
913 var equalityTester = this.equalityTesters_[i];
914 var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
915 if (result !== jasmine.undefined) return result;
916 }
917
918 if (a === b) return true;
919
920 if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
921 return (a == jasmine.undefined && b == jasmine.undefined);
922 }
923
924 if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
925 return a === b;
926 }
927
928 if (a instanceof Date && b instanceof Date) {
929 return a.getTime() == b.getTime();
930 }
931
932 if (a.jasmineMatches) {
933 return a.jasmineMatches(b);
934 }
935
936 if (b.jasmineMatches) {
937 return b.jasmineMatches(a);
938 }
939
940 if (a instanceof jasmine.Matchers.ObjectContaining) {
941 return a.matches(b);
942 }
943
944 if (b instanceof jasmine.Matchers.ObjectContaining) {
945 return b.matches(a);
946 }
947
948 if (jasmine.isString_(a) && jasmine.isString_(b)) {
949 return (a == b);
950 }
951
952 if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
953 return (a == b);
954 }
955
956 if (typeof a === "object" && typeof b === "object") {
957 return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
958 }
959
960 //Straight check
961 return (a === b);
962 };
963
964 jasmine.Env.prototype.contains_ = function(haystack, needle) {
965 if (jasmine.isArray_(haystack)) {
966 for (var i = 0; i < haystack.length; i++) {
967 if (this.equals_(haystack[i], needle)) return true;
968 }
969 return false;
970 }
971 return haystack.indexOf(needle) >= 0;
972 };
973
974 jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
975 this.equalityTesters_.push(equalityTester);
976 };
977 /** No-op base class for Jasmine reporters.
978 *
979 * @constructor
980 */
981 jasmine.Reporter = function() {
982 };
983
984 //noinspection JSUnusedLocalSymbols
985 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
986 };
987
988 //noinspection JSUnusedLocalSymbols
989 jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
990 };
991
992 //noinspection JSUnusedLocalSymbols
993 jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
994 };
995
996 //noinspection JSUnusedLocalSymbols
997 jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
998 };
999
1000 //noinspection JSUnusedLocalSymbols
1001 jasmine.Reporter.prototype.reportSpecResults = function(spec) {
1002 };
1003
1004 //noinspection JSUnusedLocalSymbols
1005 jasmine.Reporter.prototype.log = function(str) {
1006 };
1007
1008 /**
1009 * Blocks are functions with executable code that make up a spec.
1010 *
1011 * @constructor
1012 * @param {jasmine.Env} env
1013 * @param {Function} func
1014 * @param {jasmine.Spec} spec
1015 */
1016 jasmine.Block = function(env, func, spec) {
1017 this.env = env;
1018 this.func = func;
1019 this.spec = spec;
1020 };
1021
1022 jasmine.Block.prototype.execute = function(onComplete) {
1023 try {
1024 this.func.apply(this.spec);
1025 } catch (e) {
1026 this.spec.fail(e);
1027 }
1028 onComplete();
1029 };
1030 /** JavaScript API reporter.
1031 *
1032 * @constructor
1033 */
1034 jasmine.JsApiReporter = function() {
1035 this.started = false;
1036 this.finished = false;
1037 this.suites_ = [];
1038 this.results_ = {};
1039 };
1040
1041 jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
1042 this.started = true;
1043 var suites = runner.topLevelSuites();
1044 for (var i = 0; i < suites.length; i++) {
1045 var suite = suites[i];
1046 this.suites_.push(this.summarize_(suite));
1047 }
1048 };
1049
1050 jasmine.JsApiReporter.prototype.suites = function() {
1051 return this.suites_;
1052 };
1053
1054 jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
1055 var isSuite = suiteOrSpec instanceof jasmine.Suite;
1056 var summary = {
1057 id: suiteOrSpec.id,
1058 name: suiteOrSpec.description,
1059 type: isSuite ? 'suite' : 'spec',
1060 children: []
1061 };
1062
1063 if (isSuite) {
1064 var children = suiteOrSpec.children();
1065 for (var i = 0; i < children.length; i++) {
1066 summary.children.push(this.summarize_(children[i]));
1067 }
1068 }
1069 return summary;
1070 };
1071
1072 jasmine.JsApiReporter.prototype.results = function() {
1073 return this.results_;
1074 };
1075
1076 jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1077 return this.results_[specId];
1078 };
1079
1080 //noinspection JSUnusedLocalSymbols
1081 jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1082 this.finished = true;
1083 };
1084
1085 //noinspection JSUnusedLocalSymbols
1086 jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1087 };
1088
1089 //noinspection JSUnusedLocalSymbols
1090 jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1091 this.results_[spec.id] = {
1092 messages: spec.results().getItems(),
1093 result: spec.results().failedCount > 0 ? "failed" : "passed"
1094 };
1095 };
1096
1097 //noinspection JSUnusedLocalSymbols
1098 jasmine.JsApiReporter.prototype.log = function(str) {
1099 };
1100
1101 jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1102 var results = {};
1103 for (var i = 0; i < specIds.length; i++) {
1104 var specId = specIds[i];
1105 results[specId] = this.summarizeResult_(this.results_[specId]);
1106 }
1107 return results;
1108 };
1109
1110 jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1111 var summaryMessages = [];
1112 var messagesLength = result.messages.length;
1113 for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1114 var resultMessage = result.messages[messageIndex];
1115 summaryMessages.push({
1116 text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1117 passed: resultMessage.passed ? resultMessage.passed() : true,
1118 type: resultMessage.type,
1119 message: resultMessage.message,
1120 trace: {
1121 stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1122 }
1123 });
1124 }
1125
1126 return {
1127 result : result.result,
1128 messages : summaryMessages
1129 };
1130 };
1131
1132 /**
1133 * @constructor
1134 * @param {jasmine.Env} env
1135 * @param actual
1136 * @param {jasmine.Spec} spec
1137 */
1138 jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1139 this.env = env;
1140 this.actual = actual;
1141 this.spec = spec;
1142 this.isNot = opt_isNot || false;
1143 this.reportWasCalled_ = false;
1144 };
1145
1146 // todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1147 jasmine.Matchers.pp = function(str) {
1148 throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1149 };
1150
1151 // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1152 jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1153 throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1154 };
1155
1156 jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1157 for (var methodName in prototype) {
1158 if (methodName == 'report') continue;
1159 var orig = prototype[methodName];
1160 matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1161 }
1162 };
1163
1164 jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1165 return function() {
1166 var matcherArgs = jasmine.util.argsToArray(arguments);
1167 var result = matcherFunction.apply(this, arguments);
1168
1169 if (this.isNot) {
1170 result = !result;
1171 }
1172
1173 if (this.reportWasCalled_) return result;
1174
1175 var message;
1176 if (!result) {
1177 if (this.message) {
1178 message = this.message.apply(this, arguments);
1179 if (jasmine.isArray_(message)) {
1180 message = message[this.isNot ? 1 : 0];
1181 }
1182 } else {
1183 var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1184 message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1185 if (matcherArgs.length > 0) {
1186 for (var i = 0; i < matcherArgs.length; i++) {
1187 if (i > 0) message += ",";
1188 message += " " + jasmine.pp(matcherArgs[i]);
1189 }
1190 }
1191 message += ".";
1192 }
1193 }
1194 var expectationResult = new jasmine.ExpectationResult({
1195 matcherName: matcherName,
1196 passed: result,
1197 expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1198 actual: this.actual,
1199 message: message
1200 });
1201 this.spec.addMatcherResult(expectationResult);
1202 return jasmine.undefined;
1203 };
1204 };
1205
1206
1207
1208
1209 /**
1210 * toBe: compares the actual to the expected using ===
1211 * @param expected
1212 */
1213 jasmine.Matchers.prototype.toBe = function(expected) {
1214 return this.actual === expected;
1215 };
1216
1217 /**
1218 * toNotBe: compares the actual to the expected using !==
1219 * @param expected
1220 * @deprecated as of 1.0. Use not.toBe() instead.
1221 */
1222 jasmine.Matchers.prototype.toNotBe = function(expected) {
1223 return this.actual !== expected;
1224 };
1225
1226 /**
1227 * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1228 *
1229 * @param expected
1230 */
1231 jasmine.Matchers.prototype.toEqual = function(expected) {
1232 return this.env.equals_(this.actual, expected);
1233 };
1234
1235 /**
1236 * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1237 * @param expected
1238 * @deprecated as of 1.0. Use not.toEqual() instead.
1239 */
1240 jasmine.Matchers.prototype.toNotEqual = function(expected) {
1241 return !this.env.equals_(this.actual, expected);
1242 };
1243
1244 /**
1245 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
1246 * a pattern or a String.
1247 *
1248 * @param expected
1249 */
1250 jasmine.Matchers.prototype.toMatch = function(expected) {
1251 return new RegExp(expected).test(this.actual);
1252 };
1253
1254 /**
1255 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1256 * @param expected
1257 * @deprecated as of 1.0. Use not.toMatch() instead.
1258 */
1259 jasmine.Matchers.prototype.toNotMatch = function(expected) {
1260 return !(new RegExp(expected).test(this.actual));
1261 };
1262
1263 /**
1264 * Matcher that compares the actual to jasmine.undefined.
1265 */
1266 jasmine.Matchers.prototype.toBeDefined = function() {
1267 return (this.actual !== jasmine.undefined);
1268 };
1269
1270 /**
1271 * Matcher that compares the actual to jasmine.undefined.
1272 */
1273 jasmine.Matchers.prototype.toBeUndefined = function() {
1274 return (this.actual === jasmine.undefined);
1275 };
1276
1277 /**
1278 * Matcher that compares the actual to null.
1279 */
1280 jasmine.Matchers.prototype.toBeNull = function() {
1281 return (this.actual === null);
1282 };
1283
1284 /**
1285 * Matcher that boolean not-nots the actual.
1286 */
1287 jasmine.Matchers.prototype.toBeTruthy = function() {
1288 return !!this.actual;
1289 };
1290
1291
1292 /**
1293 * Matcher that boolean nots the actual.
1294 */
1295 jasmine.Matchers.prototype.toBeFalsy = function() {
1296 return !this.actual;
1297 };
1298
1299
1300 /**
1301 * Matcher that checks to see if the actual, a Jasmine spy, was called.
1302 */
1303 jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1304 if (arguments.length > 0) {
1305 throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1306 }
1307
1308 if (!jasmine.isSpy(this.actual)) {
1309 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1310 }
1311
1312 this.message = function() {
1313 return [
1314 "Expected spy " + this.actual.identity + " to have been called.",
1315 "Expected spy " + this.actual.identity + " not to have been called."
1316 ];
1317 };
1318
1319 return this.actual.wasCalled;
1320 };
1321
1322 /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1323 jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1324
1325 /**
1326 * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1327 *
1328 * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1329 */
1330 jasmine.Matchers.prototype.wasNotCalled = function() {
1331 if (arguments.length > 0) {
1332 throw new Error('wasNotCalled does not take arguments');
1333 }
1334
1335 if (!jasmine.isSpy(this.actual)) {
1336 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1337 }
1338
1339 this.message = function() {
1340 return [
1341 "Expected spy " + this.actual.identity + " to not have been called.",
1342 "Expected spy " + this.actual.identity + " to have been called."
1343 ];
1344 };
1345
1346 return !this.actual.wasCalled;
1347 };
1348
1349 /**
1350 * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1351 *
1352 * @example
1353 *
1354 */
1355 jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1356 var expectedArgs = jasmine.util.argsToArray(arguments);
1357 if (!jasmine.isSpy(this.actual)) {
1358 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1359 }
1360 this.message = function() {
1361 if (this.actual.callCount === 0) {
1362 // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
1363 return [
1364 "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
1365 "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
1366 ];
1367 } else {
1368 return [
1369 "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
1370 "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
1371 ];
1372 }
1373 };
1374
1375 return this.env.contains_(this.actual.argsForCall, expectedArgs);
1376 };
1377
1378 /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1379 jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1380
1381 /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1382 jasmine.Matchers.prototype.wasNotCalledWith = function() {
1383 var expectedArgs = jasmine.util.argsToArray(arguments);
1384 if (!jasmine.isSpy(this.actual)) {
1385 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1386 }
1387
1388 this.message = function() {
1389 return [
1390 "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1391 "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1392 ];
1393 };
1394
1395 return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1396 };
1397
1398 /**
1399 * Matcher that checks that the expected item is an element in the actual Array.
1400 *
1401 * @param {Object} expected
1402 */
1403 jasmine.Matchers.prototype.toContain = function(expected) {
1404 return this.env.contains_(this.actual, expected);
1405 };
1406
1407 /**
1408 * Matcher that checks that the expected item is NOT an element in the actual Array.
1409 *
1410 * @param {Object} expected
1411 * @deprecated as of 1.0. Use not.toContain() instead.
1412 */
1413 jasmine.Matchers.prototype.toNotContain = function(expected) {
1414 return !this.env.contains_(this.actual, expected);
1415 };
1416
1417 jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1418 return this.actual < expected;
1419 };
1420
1421 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1422 return this.actual > expected;
1423 };
1424
1425 /**
1426 * Matcher that checks that the expected item is equal to the actual item
1427 * up to a given level of decimal precision (default 2).
1428 *
1429 * @param {Number} expected
1430 * @param {Number} precision
1431 */
1432 jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
1433 if (!(precision === 0)) {
1434 precision = precision || 2;
1435 }
1436 var multiplier = Math.pow(10, precision);
1437 var actual = Math.round(this.actual * multiplier);
1438 expected = Math.round(expected * multiplier);
1439 return expected == actual;
1440 };
1441
1442 /**
1443 * Matcher that checks that the expected exception was thrown by the actual.
1444 *
1445 * @param {String} expected
1446 */
1447 jasmine.Matchers.prototype.toThrow = function(expected) {
1448 var result = false;
1449 var exception;
1450 if (typeof this.actual != 'function') {
1451 throw new Error('Actual is not a function');
1452 }
1453 try {
1454 this.actual();
1455 } catch (e) {
1456 exception = e;
1457 }
1458 if (exception) {
1459 result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1460 }
1461
1462 var not = this.isNot ? "not " : "";
1463
1464 this.message = function() {
1465 if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1466 return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
1467 } else {
1468 return "Expected function to throw an exception.";
1469 }
1470 };
1471
1472 return result;
1473 };
1474
1475 jasmine.Matchers.Any = function(expectedClass) {
1476 this.expectedClass = expectedClass;
1477 };
1478
1479 jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
1480 if (this.expectedClass == String) {
1481 return typeof other == 'string' || other instanceof String;
1482 }
1483
1484 if (this.expectedClass == Number) {
1485 return typeof other == 'number' || other instanceof Number;
1486 }
1487
1488 if (this.expectedClass == Function) {
1489 return typeof other == 'function' || other instanceof Function;
1490 }
1491
1492 if (this.expectedClass == Object) {
1493 return typeof other == 'object';
1494 }
1495
1496 return other instanceof this.expectedClass;
1497 };
1498
1499 jasmine.Matchers.Any.prototype.jasmineToString = function() {
1500 return '<jasmine.any(' + this.expectedClass + ')>';
1501 };
1502
1503 jasmine.Matchers.ObjectContaining = function (sample) {
1504 this.sample = sample;
1505 };
1506
1507 jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
1508 mismatchKeys = mismatchKeys || [];
1509 mismatchValues = mismatchValues || [];
1510
1511 var env = jasmine.getEnv();
1512
1513 var hasKey = function(obj, keyName) {
1514 return obj != null && obj[keyName] !== jasmine.undefined;
1515 };
1516
1517 for (var property in this.sample) {
1518 if (!hasKey(other, property) && hasKey(this.sample, property)) {
1519 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1520 }
1521 else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
1522 mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
1523 }
1524 }
1525
1526 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1527 };
1528
1529 jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
1530 return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
1531 };
1532 // Mock setTimeout, clearTimeout
1533 // Contributed by Pivotal Computer Systems, www.pivotalsf.com
1534
1535 jasmine.FakeTimer = function() {
1536 this.reset();
1537
1538 var self = this;
1539 self.setTimeout = function(funcToCall, millis) {
1540 self.timeoutsMade++;
1541 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
1542 return self.timeoutsMade;
1543 };
1544
1545 self.setInterval = function(funcToCall, millis) {
1546 self.timeoutsMade++;
1547 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
1548 return self.timeoutsMade;
1549 };
1550
1551 self.clearTimeout = function(timeoutKey) {
1552 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1553 };
1554
1555 self.clearInterval = function(timeoutKey) {
1556 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1557 };
1558
1559 };
1560
1561 jasmine.FakeTimer.prototype.reset = function() {
1562 this.timeoutsMade = 0;
1563 this.scheduledFunctions = {};
1564 this.nowMillis = 0;
1565 };
1566
1567 jasmine.FakeTimer.prototype.tick = function(millis) {
1568 var oldMillis = this.nowMillis;
1569 var newMillis = oldMillis + millis;
1570 this.runFunctionsWithinRange(oldMillis, newMillis);
1571 this.nowMillis = newMillis;
1572 };
1573
1574 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
1575 var scheduledFunc;
1576 var funcsToRun = [];
1577 for (var timeoutKey in this.scheduledFunctions) {
1578 scheduledFunc = this.scheduledFunctions[timeoutKey];
1579 if (scheduledFunc != jasmine.undefined &&
1580 scheduledFunc.runAtMillis >= oldMillis &&
1581 scheduledFunc.runAtMillis <= nowMillis) {
1582 funcsToRun.push(scheduledFunc);
1583 this.scheduledFunctions[timeoutKey] = jasmine.undefined;
1584 }
1585 }
1586
1587 if (funcsToRun.length > 0) {
1588 funcsToRun.sort(function(a, b) {
1589 return a.runAtMillis - b.runAtMillis;
1590 });
1591 for (var i = 0; i < funcsToRun.length; ++i) {
1592 try {
1593 var funcToRun = funcsToRun[i];
1594 this.nowMillis = funcToRun.runAtMillis;
1595 funcToRun.funcToCall();
1596 if (funcToRun.recurring) {
1597 this.scheduleFunction(funcToRun.timeoutKey,
1598 funcToRun.funcToCall,
1599 funcToRun.millis,
1600 true);
1601 }
1602 } catch(e) {
1603 }
1604 }
1605 this.runFunctionsWithinRange(oldMillis, nowMillis);
1606 }
1607 };
1608
1609 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
1610 this.scheduledFunctions[timeoutKey] = {
1611 runAtMillis: this.nowMillis + millis,
1612 funcToCall: funcToCall,
1613 recurring: recurring,
1614 timeoutKey: timeoutKey,
1615 millis: millis
1616 };
1617 };
1618
1619 /**
1620 * @namespace
1621 */
1622 jasmine.Clock = {
1623 defaultFakeTimer: new jasmine.FakeTimer(),
1624
1625 reset: function() {
1626 jasmine.Clock.assertInstalled();
1627 jasmine.Clock.defaultFakeTimer.reset();
1628 },
1629
1630 tick: function(millis) {
1631 jasmine.Clock.assertInstalled();
1632 jasmine.Clock.defaultFakeTimer.tick(millis);
1633 },
1634
1635 runFunctionsWithinRange: function(oldMillis, nowMillis) {
1636 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
1637 },
1638
1639 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
1640 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
1641 },
1642
1643 useMock: function() {
1644 if (!jasmine.Clock.isInstalled()) {
1645 var spec = jasmine.getEnv().currentSpec;
1646 spec.after(jasmine.Clock.uninstallMock);
1647
1648 jasmine.Clock.installMock();
1649 }
1650 },
1651
1652 installMock: function() {
1653 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
1654 },
1655
1656 uninstallMock: function() {
1657 jasmine.Clock.assertInstalled();
1658 jasmine.Clock.installed = jasmine.Clock.real;
1659 },
1660
1661 real: {
1662 setTimeout: jasmine.getGlobal().setTimeout,
1663 clearTimeout: jasmine.getGlobal().clearTimeout,
1664 setInterval: jasmine.getGlobal().setInterval,
1665 clearInterval: jasmine.getGlobal().clearInterval
1666 },
1667
1668 assertInstalled: function() {
1669 if (!jasmine.Clock.isInstalled()) {
1670 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
1671 }
1672 },
1673
1674 isInstalled: function() {
1675 return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
1676 },
1677
1678 installed: null
1679 };
1680 jasmine.Clock.installed = jasmine.Clock.real;
1681
1682 //else for IE support
1683 jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
1684 if (jasmine.Clock.installed.setTimeout.apply) {
1685 return jasmine.Clock.installed.setTimeout.apply(this, arguments);
1686 } else {
1687 return jasmine.Clock.installed.setTimeout(funcToCall, millis);
1688 }
1689 };
1690
1691 jasmine.getGlobal().setInterval = function(funcToCall, millis) {
1692 if (jasmine.Clock.installed.setInterval.apply) {
1693 return jasmine.Clock.installed.setInterval.apply(this, arguments);
1694 } else {
1695 return jasmine.Clock.installed.setInterval(funcToCall, millis);
1696 }
1697 };
1698
1699 jasmine.getGlobal().clearTimeout = function(timeoutKey) {
1700 if (jasmine.Clock.installed.clearTimeout.apply) {
1701 return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
1702 } else {
1703 return jasmine.Clock.installed.clearTimeout(timeoutKey);
1704 }
1705 };
1706
1707 jasmine.getGlobal().clearInterval = function(timeoutKey) {
1708 if (jasmine.Clock.installed.clearTimeout.apply) {
1709 return jasmine.Clock.installed.clearInterval.apply(this, arguments);
1710 } else {
1711 return jasmine.Clock.installed.clearInterval(timeoutKey);
1712 }
1713 };
1714
1715 /**
1716 * @constructor
1717 */
1718 jasmine.MultiReporter = function() {
1719 this.subReporters_ = [];
1720 };
1721 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
1722
1723 jasmine.MultiReporter.prototype.addReporter = function(reporter) {
1724 this.subReporters_.push(reporter);
1725 };
1726
1727 (function() {
1728 var functionNames = [
1729 "reportRunnerStarting",
1730 "reportRunnerResults",
1731 "reportSuiteResults",
1732 "reportSpecStarting",
1733 "reportSpecResults",
1734 "log"
1735 ];
1736 for (var i = 0; i < functionNames.length; i++) {
1737 var functionName = functionNames[i];
1738 jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
1739 return function() {
1740 for (var j = 0; j < this.subReporters_.length; j++) {
1741 var subReporter = this.subReporters_[j];
1742 if (subReporter[functionName]) {
1743 subReporter[functionName].apply(subReporter, arguments);
1744 }
1745 }
1746 };
1747 })(functionName);
1748 }
1749 })();
1750 /**
1751 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
1752 *
1753 * @constructor
1754 */
1755 jasmine.NestedResults = function() {
1756 /**
1757 * The total count of results
1758 */
1759 this.totalCount = 0;
1760 /**
1761 * Number of passed results
1762 */
1763 this.passedCount = 0;
1764 /**
1765 * Number of failed results
1766 */
1767 this.failedCount = 0;
1768 /**
1769 * Was this suite/spec skipped?
1770 */
1771 this.skipped = false;
1772 /**
1773 * @ignore
1774 */
1775 this.items_ = [];
1776 };
1777
1778 /**
1779 * Roll up the result counts.
1780 *
1781 * @param result
1782 */
1783 jasmine.NestedResults.prototype.rollupCounts = function(result) {
1784 this.totalCount += result.totalCount;
1785 this.passedCount += result.passedCount;
1786 this.failedCount += result.failedCount;
1787 };
1788
1789 /**
1790 * Adds a log message.
1791 * @param values Array of message parts which will be concatenated later.
1792 */
1793 jasmine.NestedResults.prototype.log = function(values) {
1794 this.items_.push(new jasmine.MessageResult(values));
1795 };
1796
1797 /**
1798 * Getter for the results: message & results.
1799 */
1800 jasmine.NestedResults.prototype.getItems = function() {
1801 return this.items_;
1802 };
1803
1804 /**
1805 * Adds a result, tracking counts (total, passed, & failed)
1806 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
1807 */
1808 jasmine.NestedResults.prototype.addResult = function(result) {
1809 if (result.type != 'log') {
1810 if (result.items_) {
1811 this.rollupCounts(result);
1812 } else {
1813 this.totalCount++;
1814 if (result.passed()) {
1815 this.passedCount++;
1816 } else {
1817 this.failedCount++;
1818 }
1819 }
1820 }
1821 this.items_.push(result);
1822 };
1823
1824 /**
1825 * @returns {Boolean} True if <b>everything</b> below passed
1826 */
1827 jasmine.NestedResults.prototype.passed = function() {
1828 return this.passedCount === this.totalCount;
1829 };
1830 /**
1831 * Base class for pretty printing for expectation results.
1832 */
1833 jasmine.PrettyPrinter = function() {
1834 this.ppNestLevel_ = 0;
1835 };
1836
1837 /**
1838 * Formats a value in a nice, human-readable string.
1839 *
1840 * @param value
1841 */
1842 jasmine.PrettyPrinter.prototype.format = function(value) {
1843 if (this.ppNestLevel_ > 40) {
1844 throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
1845 }
1846
1847 this.ppNestLevel_++;
1848 try {
1849 if (value === jasmine.undefined) {
1850 this.emitScalar('undefined');
1851 } else if (value === null) {
1852 this.emitScalar('null');
1853 } else if (value === jasmine.getGlobal()) {
1854 this.emitScalar('<global>');
1855 } else if (value.jasmineToString) {
1856 this.emitScalar(value.jasmineToString());
1857 } else if (typeof value === 'string') {
1858 this.emitString(value);
1859 } else if (jasmine.isSpy(value)) {
1860 this.emitScalar("spy on " + value.identity);
1861 } else if (value instanceof RegExp) {
1862 this.emitScalar(value.toString());
1863 } else if (typeof value === 'function') {
1864 this.emitScalar('Function');
1865 } else if (typeof value.nodeType === 'number') {
1866 this.emitScalar('HTMLNode');
1867 } else if (value instanceof Date) {
1868 this.emitScalar('Date(' + value + ')');
1869 } else if (value.__Jasmine_been_here_before__) {
1870 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
1871 } else if (jasmine.isArray_(value) || typeof value == 'object') {
1872 value.__Jasmine_been_here_before__ = true;
1873 if (jasmine.isArray_(value)) {
1874 this.emitArray(value);
1875 } else {
1876 this.emitObject(value);
1877 }
1878 delete value.__Jasmine_been_here_before__;
1879 } else {
1880 this.emitScalar(value.toString());
1881 }
1882 } finally {
1883 this.ppNestLevel_--;
1884 }
1885 };
1886
1887 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1888 for (var property in obj) {
1889 if (property == '__Jasmine_been_here_before__') continue;
1890 fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
1891 obj.__lookupGetter__(property) !== null) : false);
1892 }
1893 };
1894
1895 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
1896 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
1897 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
1898 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
1899
1900 jasmine.StringPrettyPrinter = function() {
1901 jasmine.PrettyPrinter.call(this);
1902
1903 this.string = '';
1904 };
1905 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
1906
1907 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
1908 this.append(value);
1909 };
1910
1911 jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
1912 this.append("'" + value + "'");
1913 };
1914
1915 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
1916 this.append('[ ');
1917 for (var i = 0; i < array.length; i++) {
1918 if (i > 0) {
1919 this.append(', ');
1920 }
1921 this.format(array[i]);
1922 }
1923 this.append(' ]');
1924 };
1925
1926 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
1927 var self = this;
1928 this.append('{ ');
1929 var first = true;
1930
1931 this.iterateObject(obj, function(property, isGetter) {
1932 if (first) {
1933 first = false;
1934 } else {
1935 self.append(', ');
1936 }
1937
1938 self.append(property);
1939 self.append(' : ');
1940 if (isGetter) {
1941 self.append('<getter>');
1942 } else {
1943 self.format(obj[property]);
1944 }
1945 });
1946
1947 this.append(' }');
1948 };
1949
1950 jasmine.StringPrettyPrinter.prototype.append = function(value) {
1951 this.string += value;
1952 };
1953 jasmine.Queue = function(env) {
1954 this.env = env;
1955 this.blocks = [];
1956 this.running = false;
1957 this.index = 0;
1958 this.offset = 0;
1959 this.abort = false;
1960 };
1961
1962 jasmine.Queue.prototype.addBefore = function(block) {
1963 this.blocks.unshift(block);
1964 };
1965
1966 jasmine.Queue.prototype.add = function(block) {
1967 this.blocks.push(block);
1968 };
1969
1970 jasmine.Queue.prototype.insertNext = function(block) {
1971 this.blocks.splice((this.index + this.offset + 1), 0, block);
1972 this.offset++;
1973 };
1974
1975 jasmine.Queue.prototype.start = function(onComplete) {
1976 this.running = true;
1977 this.onComplete = onComplete;
1978 this.next_();
1979 };
1980
1981 jasmine.Queue.prototype.isRunning = function() {
1982 return this.running;
1983 };
1984
1985 jasmine.Queue.LOOP_DONT_RECURSE = true;
1986
1987 jasmine.Queue.prototype.next_ = function() {
1988 var self = this;
1989 var goAgain = true;
1990
1991 while (goAgain) {
1992 goAgain = false;
1993
1994 if (self.index < self.blocks.length && !this.abort) {
1995 var calledSynchronously = true;
1996 var completedSynchronously = false;
1997
1998 var onComplete = function () {
1999 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
2000 completedSynchronously = true;
2001 return;
2002 }
2003
2004 if (self.blocks[self.index].abort) {
2005 self.abort = true;
2006 }
2007
2008 self.offset = 0;
2009 self.index++;
2010
2011 var now = new Date().getTime();
2012 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
2013 self.env.lastUpdate = now;
2014 self.env.setTimeout(function() {
2015 self.next_();
2016 }, 0);
2017 } else {
2018 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
2019 goAgain = true;
2020 } else {
2021 self.next_();
2022 }
2023 }
2024 };
2025 self.blocks[self.index].execute(onComplete);
2026
2027 calledSynchronously = false;
2028 if (completedSynchronously) {
2029 onComplete();
2030 }
2031
2032 } else {
2033 self.running = false;
2034 if (self.onComplete) {
2035 self.onComplete();
2036 }
2037 }
2038 }
2039 };
2040
2041 jasmine.Queue.prototype.results = function() {
2042 var results = new jasmine.NestedResults();
2043 for (var i = 0; i < this.blocks.length; i++) {
2044 if (this.blocks[i].results) {
2045 results.addResult(this.blocks[i].results());
2046 }
2047 }
2048 return results;
2049 };
2050
2051
2052 /**
2053 * Runner
2054 *
2055 * @constructor
2056 * @param {jasmine.Env} env
2057 */
2058 jasmine.Runner = function(env) {
2059 var self = this;
2060 self.env = env;
2061 self.queue = new jasmine.Queue(env);
2062 self.before_ = [];
2063 self.after_ = [];
2064 self.suites_ = [];
2065 };
2066
2067 jasmine.Runner.prototype.execute = function() {
2068 var self = this;
2069 if (self.env.reporter.reportRunnerStarting) {
2070 self.env.reporter.reportRunnerStarting(this);
2071 }
2072 self.queue.start(function () {
2073 self.finishCallback();
2074 });
2075 };
2076
2077 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
2078 beforeEachFunction.typeName = 'beforeEach';
2079 this.before_.splice(0,0,beforeEachFunction);
2080 };
2081
2082 jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
2083 afterEachFunction.typeName = 'afterEach';
2084 this.after_.splice(0,0,afterEachFunction);
2085 };
2086
2087
2088 jasmine.Runner.prototype.finishCallback = function() {
2089 this.env.reporter.reportRunnerResults(this);
2090 };
2091
2092 jasmine.Runner.prototype.addSuite = function(suite) {
2093 this.suites_.push(suite);
2094 };
2095
2096 jasmine.Runner.prototype.add = function(block) {
2097 if (block instanceof jasmine.Suite) {
2098 this.addSuite(block);
2099 }
2100 this.queue.add(block);
2101 };
2102
2103 jasmine.Runner.prototype.specs = function () {
2104 var suites = this.suites();
2105 var specs = [];
2106 for (var i = 0; i < suites.length; i++) {
2107 specs = specs.concat(suites[i].specs());
2108 }
2109 return specs;
2110 };
2111
2112 jasmine.Runner.prototype.suites = function() {
2113 return this.suites_;
2114 };
2115
2116 jasmine.Runner.prototype.topLevelSuites = function() {
2117 var topLevelSuites = [];
2118 for (var i = 0; i < this.suites_.length; i++) {
2119 if (!this.suites_[i].parentSuite) {
2120 topLevelSuites.push(this.suites_[i]);
2121 }
2122 }
2123 return topLevelSuites;
2124 };
2125
2126 jasmine.Runner.prototype.results = function() {
2127 return this.queue.results();
2128 };
2129 /**
2130 * Internal representation of a Jasmine specification, or test.
2131 *
2132 * @constructor
2133 * @param {jasmine.Env} env
2134 * @param {jasmine.Suite} suite
2135 * @param {String} description
2136 */
2137 jasmine.Spec = function(env, suite, description) {
2138 if (!env) {
2139 throw new Error('jasmine.Env() required');
2140 }
2141 if (!suite) {
2142 throw new Error('jasmine.Suite() required');
2143 }
2144 var spec = this;
2145 spec.id = env.nextSpecId ? env.nextSpecId() : null;
2146 spec.env = env;
2147 spec.suite = suite;
2148 spec.description = description;
2149 spec.queue = new jasmine.Queue(env);
2150
2151 spec.afterCallbacks = [];
2152 spec.spies_ = [];
2153
2154 spec.results_ = new jasmine.NestedResults();
2155 spec.results_.description = description;
2156 spec.matchersClass = null;
2157 };
2158
2159 jasmine.Spec.prototype.getFullName = function() {
2160 return this.suite.getFullName() + ' ' + this.description + '.';
2161 };
2162
2163
2164 jasmine.Spec.prototype.results = function() {
2165 return this.results_;
2166 };
2167
2168 /**
2169 * All parameters are pretty-printed and concatenated together, then written to the spec's output.
2170 *
2171 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
2172 */
2173 jasmine.Spec.prototype.log = function() {
2174 return this.results_.log(arguments);
2175 };
2176
2177 jasmine.Spec.prototype.runs = function (func) {
2178 var block = new jasmine.Block(this.env, func, this);
2179 this.addToQueue(block);
2180 return this;
2181 };
2182
2183 jasmine.Spec.prototype.addToQueue = function (block) {
2184 if (this.queue.isRunning()) {
2185 this.queue.insertNext(block);
2186 } else {
2187 this.queue.add(block);
2188 }
2189 };
2190
2191 /**
2192 * @param {jasmine.ExpectationResult} result
2193 */
2194 jasmine.Spec.prototype.addMatcherResult = function(result) {
2195 this.results_.addResult(result);
2196 };
2197
2198 jasmine.Spec.prototype.expect = function(actual) {
2199 var positive = new (this.getMatchersClass_())(this.env, actual, this);
2200 positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
2201 return positive;
2202 };
2203
2204 /**
2205 * Waits a fixed time period before moving to the next block.
2206 *
2207 * @deprecated Use waitsFor() instead
2208 * @param {Number} timeout milliseconds to wait
2209 */
2210 jasmine.Spec.prototype.waits = function(timeout) {
2211 var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
2212 this.addToQueue(waitsFunc);
2213 return this;
2214 };
2215
2216 /**
2217 * Waits for the latchFunction to return true before proceeding to the next block.
2218 *
2219 * @param {Function} latchFunction
2220 * @param {String} optional_timeoutMessage
2221 * @param {Number} optional_timeout
2222 */
2223 jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
2224 var latchFunction_ = null;
2225 var optional_timeoutMessage_ = null;
2226 var optional_timeout_ = null;
2227
2228 for (var i = 0; i < arguments.length; i++) {
2229 var arg = arguments[i];
2230 switch (typeof arg) {
2231 case 'function':
2232 latchFunction_ = arg;
2233 break;
2234 case 'string':
2235 optional_timeoutMessage_ = arg;
2236 break;
2237 case 'number':
2238 optional_timeout_ = arg;
2239 break;
2240 }
2241 }
2242
2243 var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
2244 this.addToQueue(waitsForFunc);
2245 return this;
2246 };
2247
2248 jasmine.Spec.prototype.fail = function (e) {
2249 var expectationResult = new jasmine.ExpectationResult({
2250 passed: false,
2251 message: e ? jasmine.util.formatException(e) : 'Exception',
2252 trace: { stack: e.stack }
2253 });
2254 this.results_.addResult(expectationResult);
2255 };
2256
2257 jasmine.Spec.prototype.getMatchersClass_ = function() {
2258 return this.matchersClass || this.env.matchersClass;
2259 };
2260
2261 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
2262 var parent = this.getMatchersClass_();
2263 var newMatchersClass = function() {
2264 parent.apply(this, arguments);
2265 };
2266 jasmine.util.inherit(newMatchersClass, parent);
2267 jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
2268 this.matchersClass = newMatchersClass;
2269 };
2270
2271 jasmine.Spec.prototype.finishCallback = function() {
2272 this.env.reporter.reportSpecResults(this);
2273 };
2274
2275 jasmine.Spec.prototype.finish = function(onComplete) {
2276 this.removeAllSpies();
2277 this.finishCallback();
2278 if (onComplete) {
2279 onComplete();
2280 }
2281 };
2282
2283 jasmine.Spec.prototype.after = function(doAfter) {
2284 if (this.queue.isRunning()) {
2285 this.queue.add(new jasmine.Block(this.env, doAfter, this));
2286 } else {
2287 this.afterCallbacks.unshift(doAfter);
2288 }
2289 };
2290
2291 jasmine.Spec.prototype.execute = function(onComplete) {
2292 var spec = this;
2293 if (!spec.env.specFilter(spec)) {
2294 spec.results_.skipped = true;
2295 spec.finish(onComplete);
2296 return;
2297 }
2298
2299 this.env.reporter.reportSpecStarting(this);
2300
2301 spec.env.currentSpec = spec;
2302
2303 spec.addBeforesAndAftersToQueue();
2304
2305 spec.queue.start(function () {
2306 spec.finish(onComplete);
2307 });
2308 };
2309
2310 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
2311 var runner = this.env.currentRunner();
2312 var i;
2313
2314 for (var suite = this.suite; suite; suite = suite.parentSuite) {
2315 for (i = 0; i < suite.before_.length; i++) {
2316 this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
2317 }
2318 }
2319 for (i = 0; i < runner.before_.length; i++) {
2320 this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
2321 }
2322 for (i = 0; i < this.afterCallbacks.length; i++) {
2323 this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
2324 }
2325 for (suite = this.suite; suite; suite = suite.parentSuite) {
2326 for (i = 0; i < suite.after_.length; i++) {
2327 this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
2328 }
2329 }
2330 for (i = 0; i < runner.after_.length; i++) {
2331 this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
2332 }
2333 };
2334
2335 jasmine.Spec.prototype.explodes = function() {
2336 throw 'explodes function should not have been called';
2337 };
2338
2339 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
2340 if (obj == jasmine.undefined) {
2341 throw "spyOn could not find an object to spy upon for " + methodName + "()";
2342 }
2343
2344 if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
2345 throw methodName + '() method does not exist';
2346 }
2347
2348 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
2349 throw new Error(methodName + ' has already been spied upon');
2350 }
2351
2352 var spyObj = jasmine.createSpy(methodName);
2353
2354 this.spies_.push(spyObj);
2355 spyObj.baseObj = obj;
2356 spyObj.methodName = methodName;
2357 spyObj.originalValue = obj[methodName];
2358
2359 obj[methodName] = spyObj;
2360
2361 return spyObj;
2362 };
2363
2364 jasmine.Spec.prototype.removeAllSpies = function() {
2365 for (var i = 0; i < this.spies_.length; i++) {
2366 var spy = this.spies_[i];
2367 spy.baseObj[spy.methodName] = spy.originalValue;
2368 }
2369 this.spies_ = [];
2370 };
2371
2372 /**
2373 * Internal representation of a Jasmine suite.
2374 *
2375 * @constructor
2376 * @param {jasmine.Env} env
2377 * @param {String} description
2378 * @param {Function} specDefinitions
2379 * @param {jasmine.Suite} parentSuite
2380 */
2381 jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
2382 var self = this;
2383 self.id = env.nextSuiteId ? env.nextSuiteId() : null;
2384 self.description = description;
2385 self.queue = new jasmine.Queue(env);
2386 self.parentSuite = parentSuite;
2387 self.env = env;
2388 self.before_ = [];
2389 self.after_ = [];
2390 self.children_ = [];
2391 self.suites_ = [];
2392 self.specs_ = [];
2393 };
2394
2395 jasmine.Suite.prototype.getFullName = function() {
2396 var fullName = this.description;
2397 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2398 fullName = parentSuite.description + ' ' + fullName;
2399 }
2400 return fullName;
2401 };
2402
2403 jasmine.Suite.prototype.finish = function(onComplete) {
2404 this.env.reporter.reportSuiteResults(this);
2405 this.finished = true;
2406 if (typeof(onComplete) == 'function') {
2407 onComplete();
2408 }
2409 };
2410
2411 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
2412 beforeEachFunction.typeName = 'beforeEach';
2413 this.before_.unshift(beforeEachFunction);
2414 };
2415
2416 jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
2417 afterEachFunction.typeName = 'afterEach';
2418 this.after_.unshift(afterEachFunction);
2419 };
2420
2421 jasmine.Suite.prototype.results = function() {
2422 return this.queue.results();
2423 };
2424
2425 jasmine.Suite.prototype.add = function(suiteOrSpec) {
2426 this.children_.push(suiteOrSpec);
2427 if (suiteOrSpec instanceof jasmine.Suite) {
2428 this.suites_.push(suiteOrSpec);
2429 this.env.currentRunner().addSuite(suiteOrSpec);
2430 } else {
2431 this.specs_.push(suiteOrSpec);
2432 }
2433 this.queue.add(suiteOrSpec);
2434 };
2435
2436 jasmine.Suite.prototype.specs = function() {
2437 return this.specs_;
2438 };
2439
2440 jasmine.Suite.prototype.suites = function() {
2441 return this.suites_;
2442 };
2443
2444 jasmine.Suite.prototype.children = function() {
2445 return this.children_;
2446 };
2447
2448 jasmine.Suite.prototype.execute = function(onComplete) {
2449 var self = this;
2450 this.queue.start(function () {
2451 self.finish(onComplete);
2452 });
2453 };
2454 jasmine.WaitsBlock = function(env, timeout, spec) {
2455 this.timeout = timeout;
2456 jasmine.Block.call(this, env, null, spec);
2457 };
2458
2459 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
2460
2461 jasmine.WaitsBlock.prototype.execute = function (onComplete) {
2462 if (jasmine.VERBOSE) {
2463 this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
2464 }
2465 this.env.setTimeout(function () {
2466 onComplete();
2467 }, this.timeout);
2468 };
2469 /**
2470 * A block which waits for some condition to become true, with timeout.
2471 *
2472 * @constructor
2473 * @extends jasmine.Block
2474 * @param {jasmine.Env} env The Jasmine environment.
2475 * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
2476 * @param {Function} latchFunction A function which returns true when the desired condition has been met.
2477 * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
2478 * @param {jasmine.Spec} spec The Jasmine spec.
2479 */
2480 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
2481 this.timeout = timeout || env.defaultTimeoutInterval;
2482 this.latchFunction = latchFunction;
2483 this.message = message;
2484 this.totalTimeSpentWaitingForLatch = 0;
2485 jasmine.Block.call(this, env, null, spec);
2486 };
2487 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
2488
2489 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
2490
2491 jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
2492 if (jasmine.VERBOSE) {
2493 this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
2494 }
2495 var latchFunctionResult;
2496 try {
2497 latchFunctionResult = this.latchFunction.apply(this.spec);
2498 } catch (e) {
2499 this.spec.fail(e);
2500 onComplete();
2501 return;
2502 }
2503
2504 if (latchFunctionResult) {
2505 onComplete();
2506 } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
2507 var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
2508 this.spec.fail({
2509 name: 'timeout',
2510 message: message
2511 });
2512
2513 this.abort = true;
2514 onComplete();
2515 } else {
2516 this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
2517 var self = this;
2518 this.env.setTimeout(function() {
2519 self.execute(onComplete);
2520 }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
2521 }
2522 };
2523
2524 jasmine.version_= {
2525 "major": 1,
2526 "minor": 2,
2527 "build": 0,
2528 "revision": 1337005947
2529 };