]> git.proxmox.com Git - extjs.git/blame - extjs/modern/modern/.sencha/test/jasmine.js
bump version to 7.0.0-4
[extjs.git] / extjs / modern / modern / .sencha / test / jasmine.js
CommitLineData
947f0963
TL
1var isCommonJS = typeof window == "undefined" && typeof exports == "object";
2
3/**
4 * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
5 *
6 * @namespace
7 */
8var jasmine = {};
9if (isCommonJS) exports.jasmine = jasmine;
10/**
11 * @private
12 */
13jasmine.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 */
23jasmine.undefined = jasmine.___undefined___;
24
25/**
26 * Show diagnostic messages in the console if set to true
27 *
28 */
29jasmine.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 */
35jasmine.DEFAULT_UPDATE_INTERVAL = 250;
36
37/**
38 * Maximum levels of nesting that will be included when an object is pretty-printed
39 */
40jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
41
42/**
43 * Default timeout interval in milliseconds for waitsFor() blocks.
44 */
45jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
46
47/**
48 * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
49 * Set to false to let the exception bubble up in the browser.
50 *
51 */
52jasmine.CATCH_EXCEPTIONS = true;
53
54jasmine.getGlobal = function() {
55 function getGlobal() {
56 return this;
57 }
58
59 return getGlobal();
60};
61
62/**
63 * Allows for bound functions to be compared. Internal use only.
64 *
65 * @ignore
66 * @private
67 * @param base {Object} bound 'this' for the function
68 * @param name {Function} function to find
69 */
70jasmine.bindOriginal_ = function(base, name) {
71 var original = base[name];
72 if (original.apply) {
73 return function() {
74 return original.apply(base, arguments);
75 };
76 } else {
77 // IE support
78 return jasmine.getGlobal()[name];
79 }
80};
81
82jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
83jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
84jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
85jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
86
87jasmine.MessageResult = function(values) {
88 this.type = 'log';
89 this.values = values;
90 this.trace = new Error(); // todo: test better
91};
92
93jasmine.MessageResult.prototype.toString = function() {
94 var text = "";
95 for (var i = 0; i < this.values.length; i++) {
96 if (i > 0) text += " ";
97 if (jasmine.isString_(this.values[i])) {
98 text += this.values[i];
99 } else {
100 text += jasmine.pp(this.values[i]);
101 }
102 }
103 return text;
104};
105
106jasmine.ExpectationResult = function(params) {
107 this.type = 'expect';
108 this.matcherName = params.matcherName;
109 this.passed_ = params.passed;
110 this.expected = params.expected;
111 this.actual = params.actual;
112 this.message = this.passed_ ? 'Passed.' : params.message;
113
114 var trace = (params.trace || new Error(this.message));
115 this.trace = this.passed_ ? '' : trace + '';
116};
117
118jasmine.ExpectationResult.prototype.toString = function () {
119 return this.message;
120};
121
122jasmine.ExpectationResult.prototype.passed = function () {
123 return this.passed_;
124};
125
126/**
127 * Getter for the Jasmine environment. Ensures one gets created
128 */
129jasmine.getEnv = function() {
130 var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
131 return env;
132};
133
134/**
135 * @ignore
136 * @private
137 * @param value
138 * @returns {Boolean}
139 */
140jasmine.isArray_ = function(value) {
141 return jasmine.isA_("Array", value);
142};
143
144/**
145 * @ignore
146 * @private
147 * @param value
148 * @returns {Boolean}
149 */
150jasmine.isString_ = function(value) {
151 return jasmine.isA_("String", value);
152};
153
154/**
155 * @ignore
156 * @private
157 * @param value
158 * @returns {Boolean}
159 */
160jasmine.isNumber_ = function(value) {
161 return jasmine.isA_("Number", value);
162};
163
164/**
165 * @ignore
166 * @private
167 * @param {String} typeName
168 * @param value
169 * @returns {Boolean}
170 */
171jasmine.isA_ = function(typeName, value) {
172 return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
173};
174
175/**
176 * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
177 *
178 * @param value {Object} an object to be outputted
179 * @returns {String}
180 */
181jasmine.pp = function(value) {
182 var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
183 stringPrettyPrinter.format(value);
184 return stringPrettyPrinter.string;
185};
186
187/**
188 * Returns true if the object is a DOM Node.
189 *
190 * @param {Object} obj object to check
191 * @returns {Boolean}
192 */
193jasmine.isDomNode = function(obj) {
194 return obj.nodeType > 0;
195};
196
197/**
198 * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
199 *
200 * @example
201 * // don't care about which function is passed in, as long as it's a function
202 * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
203 *
204 * @param {Class} clazz
205 * @returns matchable object of the type clazz
206 */
207jasmine.any = function(clazz) {
208 return new jasmine.Matchers.Any(clazz);
209};
210
211/**
212 * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
213 * attributes on the object.
214 *
215 * @example
216 * // don't care about any other attributes than foo.
217 * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
218 *
219 * @param sample {Object} sample
220 * @returns matchable object for the sample
221 */
222jasmine.objectContaining = function (sample) {
223 return new jasmine.Matchers.ObjectContaining(sample);
224};
225
226/**
227 * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
228 *
229 * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
230 * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
231 *
232 * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
233 *
234 * Spies are torn down at the end of every spec.
235 *
236 * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
237 *
238 * @example
239 * // a stub
240 * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
241 *
242 * // spy example
243 * var foo = {
244 * not: function(bool) { return !bool; }
245 * }
246 *
247 * // actual foo.not will not be called, execution stops
248 * spyOn(foo, 'not');
249
250 // foo.not spied upon, execution will continue to implementation
251 * spyOn(foo, 'not').andCallThrough();
252 *
253 * // fake example
254 * var foo = {
255 * not: function(bool) { return !bool; }
256 * }
257 *
258 * // foo.not(val) will return val
259 * spyOn(foo, 'not').andCallFake(function(value) {return value;});
260 *
261 * // mock example
262 * foo.not(7 == 7);
263 * expect(foo.not).toHaveBeenCalled();
264 * expect(foo.not).toHaveBeenCalledWith(true);
265 *
266 * @constructor
267 * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
268 * @param {String} name
269 */
270jasmine.Spy = function(name) {
271 /**
272 * The name of the spy, if provided.
273 */
274 this.identity = name || 'unknown';
275 /**
276 * Is this Object a spy?
277 */
278 this.isSpy = true;
279 /**
280 * The actual function this spy stubs.
281 */
282 this.plan = function() {
283 };
284 /**
285 * Tracking of the most recent call to the spy.
286 * @example
287 * var mySpy = jasmine.createSpy('foo');
288 * mySpy(1, 2);
289 * mySpy.mostRecentCall.args = [1, 2];
290 */
291 this.mostRecentCall = {};
292
293 /**
294 * Holds arguments for each call to the spy, indexed by call count
295 * @example
296 * var mySpy = jasmine.createSpy('foo');
297 * mySpy(1, 2);
298 * mySpy(7, 8);
299 * mySpy.mostRecentCall.args = [7, 8];
300 * mySpy.argsForCall[0] = [1, 2];
301 * mySpy.argsForCall[1] = [7, 8];
302 */
303 this.argsForCall = [];
304 this.calls = [];
305};
306
307/**
308 * Tells a spy to call through to the actual implemenatation.
309 *
310 * @example
311 * var foo = {
312 * bar: function() { // do some stuff }
313 * }
314 *
315 * // defining a spy on an existing property: foo.bar
316 * spyOn(foo, 'bar').andCallThrough();
317 */
318jasmine.Spy.prototype.andCallThrough = function() {
319 this.plan = this.originalValue;
320 return this;
321};
322
323/**
324 * For setting the return value of a spy.
325 *
326 * @example
327 * // defining a spy from scratch: foo() returns 'baz'
328 * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
329 *
330 * // defining a spy on an existing property: foo.bar() returns 'baz'
331 * spyOn(foo, 'bar').andReturn('baz');
332 *
333 * @param {Object} value
334 */
335jasmine.Spy.prototype.andReturn = function(value) {
336 this.plan = function() {
337 return value;
338 };
339 return this;
340};
341
342/**
343 * For throwing an exception when a spy is called.
344 *
345 * @example
346 * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
347 * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
348 *
349 * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
350 * spyOn(foo, 'bar').andThrow('baz');
351 *
352 * @param {String} exceptionMsg
353 */
354jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
355 this.plan = function() {
356 throw exceptionMsg;
357 };
358 return this;
359};
360
361/**
362 * Calls an alternate implementation when a spy is called.
363 *
364 * @example
365 * var baz = function() {
366 * // do some stuff, return something
367 * }
368 * // defining a spy from scratch: foo() calls the function baz
369 * var foo = jasmine.createSpy('spy on foo').andCall(baz);
370 *
371 * // defining a spy on an existing property: foo.bar() calls an anonymnous function
372 * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
373 *
374 * @param {Function} fakeFunc
375 */
376jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
377 this.plan = fakeFunc;
378 return this;
379};
380
381/**
382 * Resets all of a spy's the tracking variables so that it can be used again.
383 *
384 * @example
385 * spyOn(foo, 'bar');
386 *
387 * foo.bar();
388 *
389 * expect(foo.bar.callCount).toEqual(1);
390 *
391 * foo.bar.reset();
392 *
393 * expect(foo.bar.callCount).toEqual(0);
394 */
395jasmine.Spy.prototype.reset = function() {
396 this.wasCalled = false;
397 this.callCount = 0;
398 this.argsForCall = [];
399 this.calls = [];
400 this.mostRecentCall = {};
401};
402
403jasmine.createSpy = function(name) {
404
405 var spyObj = function() {
406 spyObj.wasCalled = true;
407 spyObj.callCount++;
408 var args = jasmine.util.argsToArray(arguments);
409 spyObj.mostRecentCall.object = this;
410 spyObj.mostRecentCall.args = args;
411 spyObj.argsForCall.push(args);
412 spyObj.calls.push({object: this, args: args});
413 return spyObj.plan.apply(this, arguments);
414 };
415
416 var spy = new jasmine.Spy(name);
417
418 for (var prop in spy) {
419 spyObj[prop] = spy[prop];
420 }
421
422 spyObj.reset();
423
424 return spyObj;
425};
426
427/**
428 * Determines whether an object is a spy.
429 *
430 * @param {jasmine.Spy|Object} putativeSpy
431 * @returns {Boolean}
432 */
433jasmine.isSpy = function(putativeSpy) {
434 return putativeSpy && putativeSpy.isSpy;
435};
436
437/**
438 * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
439 * large in one call.
440 *
441 * @param {String} baseName name of spy class
442 * @param {Array} methodNames array of names of methods to make spies
443 */
444jasmine.createSpyObj = function(baseName, methodNames) {
445 if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
446 throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
447 }
448 var obj = {};
449 for (var i = 0; i < methodNames.length; i++) {
450 obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
451 }
452 return obj;
453};
454
455/**
456 * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
457 *
458 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
459 */
460jasmine.log = function() {
461 var spec = jasmine.getEnv().currentSpec;
462 spec.log.apply(spec, arguments);
463};
464
465/**
466 * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
467 *
468 * @example
469 * // spy example
470 * var foo = {
471 * not: function(bool) { return !bool; }
472 * }
473 * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
474 *
475 * @see jasmine.createSpy
476 * @param obj
477 * @param methodName
478 * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
479 */
480var spyOn = function(obj, methodName) {
481 return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
482};
483if (isCommonJS) exports.spyOn = spyOn;
484
485/**
486 * Creates a Jasmine spec that will be added to the current suite.
487 *
488 * // TODO: pending tests
489 *
490 * @example
491 * it('should be true', function() {
492 * expect(true).toEqual(true);
493 * });
494 *
495 * @param {String} desc description of this specification
496 * @param {Function} func defines the preconditions and expectations of the spec
497 */
498var it = function(desc, func) {
499 return jasmine.getEnv().it(desc, func);
500};
501if (isCommonJS) exports.it = it;
502
503/**
504 * Creates a <em>disabled</em> Jasmine spec.
505 *
506 * A convenience method that allows existing specs to be disabled temporarily during development.
507 *
508 * @param {String} desc description of this specification
509 * @param {Function} func defines the preconditions and expectations of the spec
510 */
511var xit = function(desc, func) {
512 return jasmine.getEnv().xit(desc, func);
513};
514if (isCommonJS) exports.xit = xit;
515
516/**
517 * Starts a chain for a Jasmine expectation.
518 *
519 * It is passed an Object that is the actual value and should chain to one of the many
520 * jasmine.Matchers functions.
521 *
522 * @param {Object} actual Actual value to test against and expected value
523 * @return {jasmine.Matchers}
524 */
525var expect = function(actual) {
526 return jasmine.getEnv().currentSpec.expect(actual);
527};
528if (isCommonJS) exports.expect = expect;
529
530/**
531 * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
532 *
533 * @param {Function} func Function that defines part of a jasmine spec.
534 */
535var runs = function(func) {
536 jasmine.getEnv().currentSpec.runs(func);
537};
538if (isCommonJS) exports.runs = runs;
539
540/**
541 * Waits a fixed time period before moving to the next block.
542 *
543 * @deprecated Use waitsFor() instead
544 * @param {Number} timeout milliseconds to wait
545 */
546var waits = function(timeout) {
547 jasmine.getEnv().currentSpec.waits(timeout);
548};
549if (isCommonJS) exports.waits = waits;
550
551/**
552 * Waits for the latchFunction to return true before proceeding to the next block.
553 *
554 * @param {Function} latchFunction
555 * @param {String} optional_timeoutMessage
556 * @param {Number} optional_timeout
557 * @param {Number} optional_timeout_increment
558 */
559var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout, optional_timeout_increment) {
560 jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
561};
562if (isCommonJS) exports.waitsFor = waitsFor;
563
564/**
565 * A function that is called before each spec in a suite.
566 *
567 * Used for spec setup, including validating assumptions.
568 *
569 * @param {Function} beforeEachFunction
570 */
571var beforeEach = function(beforeEachFunction) {
572 jasmine.getEnv().beforeEach(beforeEachFunction);
573};
574if (isCommonJS) exports.beforeEach = beforeEach;
575
576/**
577 * A function that is called after each spec in a suite.
578 *
579 * Used for restoring any state that is hijacked during spec execution.
580 *
581 * @param {Function} afterEachFunction
582 */
583var afterEach = function(afterEachFunction) {
584 jasmine.getEnv().afterEach(afterEachFunction);
585};
586if (isCommonJS) exports.afterEach = afterEach;
587
588/**
589 * Defines a suite of specifications.
590 *
591 * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
592 * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
593 * of setup in some tests.
594 *
595 * @example
596 * // TODO: a simple suite
597 *
598 * // TODO: a simple suite with a nested describe block
599 *
600 * @param {String} description A string, usually the class under test.
601 * @param {Function} specDefinitions function that defines several specs.
602 */
603var describe = function(description, specDefinitions) {
604 return jasmine.getEnv().describe(description, specDefinitions);
605};
606if (isCommonJS) exports.describe = describe;
607
608/**
609 * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
610 *
611 * @param {String} description A string, usually the class under test.
612 * @param {Function} specDefinitions function that defines several specs.
613 */
614var xdescribe = function(description, specDefinitions) {
615 return jasmine.getEnv().xdescribe(description, specDefinitions);
616};
617if (isCommonJS) exports.xdescribe = xdescribe;
618
619
620// Provide the XMLHttpRequest class for IE 5.x-6.x:
621jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
622 function tryIt(f) {
623 try {
624 return f();
625 } catch(e) {
626 }
627 return null;
628 }
629
630 var xhr = tryIt(function() {
631 return new ActiveXObject("Msxml2.XMLHTTP.6.0");
632 }) ||
633 tryIt(function() {
634 return new ActiveXObject("Msxml2.XMLHTTP.3.0");
635 }) ||
636 tryIt(function() {
637 return new ActiveXObject("Msxml2.XMLHTTP");
638 }) ||
639 tryIt(function() {
640 return new ActiveXObject("Microsoft.XMLHTTP");
641 });
642
643 if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
644
645 return xhr;
646} : XMLHttpRequest;
647jasmine.MAX_PRETTY_PRINT_DEPTH = 1;
648
649jasmine.hashes = {};
650
651jasmine.hashString = function (s, hash) {
652 hash = hash || 0;
653
654 // see http://www.cse.yorku.ca/~oz/hash.html
655 for (var c, i = 0, n = s.length; i < n; ++i) {
656 c = s.charCodeAt(i);
657 hash = c + (hash << 6) + (hash << 16) - hash;
658 }
659
660 if (jasmine.hashes[hash]) {
661 jasmine.getEnv().reporter.log("Identical hash detected: " + s);
662 }
663
664 jasmine.hashes[hash] = true;
665 return hash;
666};
667
668jasmine.toMap = function (items) {
669 var map = {},
670 k;
671
672 for (k = items.length; k--; ) {
673 map[items[k]] = true;
674 }
675
676 return map;
677};
678
679// Override jasmine to track the spy call results as well
680// as their invocation arguments
681jasmine.createSpy = function(name) {
682
683 var spyObj = function() {
684 spyObj.wasCalled = true;
685 spyObj.callCount++;
686
687 var args = jasmine.util.argsToArray(arguments);
688 var result = spyObj.plan.apply(this, arguments);
689
690 spyObj.mostRecentCall.object = spyObj.mostRecentCall.scope = this;
691 spyObj.mostRecentCall.args = args;
692 spyObj.mostRecentCall.result = result;
693
694 spyObj.argsForCall.push(args);
695
696 spyObj.calls.push({object: this, scope: this, args: args, result: result});
697
698 return result;
699 };
700
701 var spy = new jasmine.Spy(name);
702
703 for (var prop in spy) {
704 spyObj[prop] = spy[prop];
705 }
706
707 spyObj.reset();
708
709 return spyObj;
710};
711
712jasmine.setCurrentScript = function(file){
713 if(typeof Ext !== "undefined" && Ext.cmd && Ext.cmd.api && Ext.cmd.api.adapter) {
714 Ext.cmd.api.adapter.setCurrentScript(file);
715 }
716};
717
718jasmine.getCurrentScript = function() {
719 if(typeof Ext !== "undefined" && Ext.cmd && Ext.cmd.api && Ext.cmd.api.adapter) {
720 return Ext.cmd.api.adapter.getCurrentScript();
721 }
722 return null;
723};
724
725jasmine.setOptions = function(jsonString) {
726 jasmine._options = JSON.parse(jsonString);
727};
728
729jasmine.getOptions = function() {
730 return jasmine._options || {};
731};
732
733jasmine.initDebug = function() {
734 var spec = jasmine.getOptions().spec;
735 if (spec) {
736 var specId = parseInt(spec);
737 this.getEnv().specFilter = function(spec) {
738 if (spec.id === specId) {
739 spec.debugBlocks = true;
740 return true;
741 } else {
742 return false;
743 }
744 }
745 }
746};
747
748jasmine.generateDebuggableBlock = function(fn) {
749 return function() {
750 debugger;
751 /* Step into the function below */
752 fn.apply(this, arguments);
753 };
754};
755
756jasmine.showDebugPrompt = function(callback) {
757 if (navigator.userAgent.toLowerCase().match(/chrome|safari|msie/)) {
758 var div = document.createElement("div");
759
760 div.setAttribute("style",[
761 "background:#E5E4E2;",
762 "z-index:99999;",
763 "position:absolute;",
764 "left:25%;",
765 "right:25%;",
766 "top:100px;",
767 "border-radius: 5px;",
768 "border:1px solid #777;",
769 "text-align:center;",
770 "box-shadow: 5px 5px 5px #888;"
771 ].join(""));
772
773 div.innerHTML = [
774 '<p>Open the developer tools to debug and press ok.</p>',
775 '<button id="sencha-debug-button">OK</button>',
776 '<p></p>'
777 ].join("");
778
779 document.body.appendChild(div);
780
781 var button = document.getElementById("sencha-debug-button");
782
783 var onClick = function() {
784 if (button.removeEventListener) {
785 button.removeEventListener("click", onClick, false);
786 } else {
787 button.detachEvent("onmousedown", onClick);
788 }
789 document.body.removeChild(div);
790 div = null;
791 callback();
792 };
793
794 if (button.addEventListener) {
795 button.addEventListener("click", onClick, false);
796 } else {
797 button.attachEvent("onmousedown", onClick);
798 }
799 } else {
800 callback();
801 }
802};
803
804jasmine.getByIds = function (items, ids) {
805 var result = [],
806 length = items.length,
807 i, id, item;
808
809 for (i = 0; i < length; ++i) {
810 item = items[i];
811 id = item.id;
812 if (ids[id]) {
813 result.push(item);
814 }
815 }
816
817 return result;
818};
819
820var specFor = function(object, specForFn) {
821 jasmine.getEnv().specFor(object, specForFn);
822};
823
824var xspecFor = function(object, specForFn) {
825 jasmine.getEnv().xspecFor(object, specForFn);
826};
827
828var xdescribe = function(description, specDefinitions, coverageFile) {
829 return jasmine.getEnv().describe(description, specDefinitions, coverageFile).disable();
830};
831
832var xit = function(desc, func) {
833 return jasmine.getEnv().it(desc, func).disable();
834};
835/**
836 * @namespace
837 */
838jasmine.util = {};
839
840/**
841 * Declare that a child class inherit it's prototype from the parent class.
842 *
843 * @private
844 * @param {Function} childClass
845 * @param {Function} parentClass
846 */
847jasmine.util.inherit = function(childClass, parentClass) {
848 /**
849 * @private
850 */
851 var subclass = function() {
852 };
853 subclass.prototype = parentClass.prototype;
854 childClass.prototype = new subclass();
855};
856
857jasmine.util.formatException = function(e) {
858 var lineNumber;
859 if (e.line) {
860 lineNumber = e.line;
861 }
862 else if (e.lineNumber) {
863 lineNumber = e.lineNumber;
864 }
865
866 var file;
867
868 if (e.sourceURL) {
869 file = e.sourceURL;
870 }
871 else if (e.fileName) {
872 file = e.fileName;
873 }
874
875 var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
876
877 if (file && lineNumber) {
878 message += ' in ' + file + ' (line ' + lineNumber + ')';
879 }
880
881 return message;
882};
883
884jasmine.util.htmlEscape = function(str) {
885 if (!str) return str;
886 return str.replace(/&/g, '&amp;')
887 .replace(/</g, '&lt;')
888 .replace(/>/g, '&gt;');
889};
890
891jasmine.util.argsToArray = function(args) {
892 var arrayOfArgs = [];
893 for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
894 return arrayOfArgs;
895};
896
897jasmine.util.extend = function(destination, source) {
898 for (var property in source) destination[property] = source[property];
899 return destination;
900};
901jasmine.util.getOrigin = function() {
902 var port = window.location.port;
903 var origin;
904
905 origin = window.location.protocol + "//" + window.location.hostname;
906
907 if (port) {
908 origin += ":" + port;
909 }
910
911 return origin;
912};
913
914jasmine.util.getFileFromContextMapping = function(file) {
915 var contextMapping = jasmine.contextMapping;
916 if (file && contextMapping) {
917 var origin = jasmine.util.getOrigin();
918 for (var context in contextMapping) {
919 file = file.replace(origin + context, contextMapping[context]);
920 }
921 }
922 return file;
923};
924
925jasmine.util.formatException = function(e) {
926 var lineNumber;
927 if (e.line) {
928 lineNumber = e.line;
929 }
930 else if (e.lineNumber) {
931 lineNumber = e.lineNumber;
932 }
933
934 var file;
935
936 if (e.sourceURL) {
937 file = e.sourceURL;
938 }
939 else if (e.fileName) {
940 file = e.fileName;
941 }
942
943 file = jasmine.util.getFileFromContextMapping(file);
944
945 var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
946
947 if (file && lineNumber) {
948 message += ' in ' + file + ' (line ' + lineNumber + ')';
949 }
950
951 return message;
952};/**
953 * Environment for Jasmine
954 *
955 * @constructor
956 */
957jasmine.Env = function() {
958 this.currentSpec = null;
959 this.currentSuite = null;
960 this.currentRunner_ = new jasmine.Runner(this);
961
962 this.reporter = new jasmine.MultiReporter();
963
964 this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
965 this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
966 this.lastUpdate = 0;
967 this.specFilter = function() {
968 return true;
969 };
970
971 this.nextSpecId_ = 0;
972 this.nextSuiteId_ = 0;
973 this.equalityTesters_ = [];
974
975 // wrap matchers
976 this.matchersClass = function() {
977 jasmine.Matchers.apply(this, arguments);
978 };
979 jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
980
981 jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
982};
983
984
985jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
986jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
987jasmine.Env.prototype.setInterval = jasmine.setInterval;
988jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
989
990/**
991 * @returns an object containing jasmine version build info, if set.
992 */
993jasmine.Env.prototype.version = function () {
994 if (jasmine.version_) {
995 return jasmine.version_;
996 } else {
997 throw new Error('Version not set');
998 }
999};
1000
1001/**
1002 * @returns string containing jasmine version build info, if set.
1003 */
1004jasmine.Env.prototype.versionString = function() {
1005 if (!jasmine.version_) {
1006 return "version unknown";
1007 }
1008
1009 var version = this.version();
1010 var versionString = version.major + "." + version.minor + "." + version.build;
1011 if (version.release_candidate) {
1012 versionString += ".rc" + version.release_candidate;
1013 }
1014 versionString += " revision " + version.revision;
1015 return versionString;
1016};
1017
1018/**
1019 * @returns a sequential integer starting at 0
1020 */
1021jasmine.Env.prototype.nextSpecId = function () {
1022 return this.nextSpecId_++;
1023};
1024
1025/**
1026 * @returns a sequential integer starting at 0
1027 */
1028jasmine.Env.prototype.nextSuiteId = function () {
1029 return this.nextSuiteId_++;
1030};
1031
1032/**
1033 * Register a reporter to receive status updates from Jasmine.
1034 * @param {jasmine.Reporter} reporter An object which will receive status updates.
1035 */
1036jasmine.Env.prototype.addReporter = function(reporter) {
1037 this.reporter.addReporter(reporter);
1038};
1039
1040jasmine.Env.prototype.execute = function() {
1041 this.currentRunner_.execute();
1042};
1043
1044jasmine.Env.prototype.describe = function(description, specDefinitions) {
1045 var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
1046
1047 var parentSuite = this.currentSuite;
1048 if (parentSuite) {
1049 parentSuite.add(suite);
1050 } else {
1051 this.currentRunner_.add(suite);
1052 }
1053
1054 this.currentSuite = suite;
1055
1056 var declarationError = null;
1057 try {
1058 specDefinitions.call(suite);
1059 } catch(e) {
1060 declarationError = e;
1061 }
1062
1063 if (declarationError) {
1064 this.it("encountered a declaration exception", function() {
1065 throw declarationError;
1066 });
1067 }
1068
1069 this.currentSuite = parentSuite;
1070
1071 return suite;
1072};
1073
1074jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
1075 if (this.currentSuite) {
1076 this.currentSuite.beforeEach(beforeEachFunction);
1077 } else {
1078 this.currentRunner_.beforeEach(beforeEachFunction);
1079 }
1080};
1081
1082jasmine.Env.prototype.currentRunner = function () {
1083 return this.currentRunner_;
1084};
1085
1086jasmine.Env.prototype.afterEach = function(afterEachFunction) {
1087 if (this.currentSuite) {
1088 this.currentSuite.afterEach(afterEachFunction);
1089 } else {
1090 this.currentRunner_.afterEach(afterEachFunction);
1091 }
1092
1093};
1094
1095jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
1096 return {
1097 execute: function() {
1098 }
1099 };
1100};
1101
1102jasmine.Env.prototype.it = function(description, func) {
1103 var spec = new jasmine.Spec(this, this.currentSuite, description);
1104 this.currentSuite.add(spec);
1105 this.currentSpec = spec;
1106
1107 if (func) {
1108 spec.runs(func);
1109 }
1110
1111 return spec;
1112};
1113
1114jasmine.Env.prototype.xit = function(desc, func) {
1115 return {
1116 id: this.nextSpecId(),
1117 runs: function() {
1118 }
1119 };
1120};
1121
1122jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
1123 if (a.source != b.source)
1124 mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
1125
1126 if (a.ignoreCase != b.ignoreCase)
1127 mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
1128
1129 if (a.global != b.global)
1130 mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
1131
1132 if (a.multiline != b.multiline)
1133 mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
1134
1135 if (a.sticky != b.sticky)
1136 mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
1137
1138 return (mismatchValues.length === 0);
1139};
1140
1141jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
1142 if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
1143 return true;
1144 }
1145
1146 a.__Jasmine_been_here_before__ = b;
1147 b.__Jasmine_been_here_before__ = a;
1148
1149 var hasKey = function(obj, keyName) {
1150 return obj !== null && obj[keyName] !== jasmine.undefined;
1151 };
1152
1153 for (var property in b) {
1154 if (!hasKey(a, property) && hasKey(b, property)) {
1155 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1156 }
1157 }
1158 for (property in a) {
1159 if (!hasKey(b, property) && hasKey(a, property)) {
1160 mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
1161 }
1162 }
1163 for (property in b) {
1164 if (property == '__Jasmine_been_here_before__') continue;
1165 if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
1166 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.");
1167 }
1168 }
1169
1170 if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
1171 mismatchValues.push("arrays were not the same length");
1172 }
1173
1174 delete a.__Jasmine_been_here_before__;
1175 delete b.__Jasmine_been_here_before__;
1176 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1177};
1178
1179jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
1180 mismatchKeys = mismatchKeys || [];
1181 mismatchValues = mismatchValues || [];
1182
1183 for (var i = 0; i < this.equalityTesters_.length; i++) {
1184 var equalityTester = this.equalityTesters_[i];
1185 var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
1186 if (result !== jasmine.undefined) return result;
1187 }
1188
1189 if (a === b) return true;
1190
1191 if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
1192 return (a == jasmine.undefined && b == jasmine.undefined);
1193 }
1194
1195 if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
1196 return a === b;
1197 }
1198
1199 if (a instanceof Date && b instanceof Date) {
1200 return a.getTime() == b.getTime();
1201 }
1202
1203 if (a.jasmineMatches) {
1204 return a.jasmineMatches(b);
1205 }
1206
1207 if (b.jasmineMatches) {
1208 return b.jasmineMatches(a);
1209 }
1210
1211 if (a instanceof jasmine.Matchers.ObjectContaining) {
1212 return a.matches(b);
1213 }
1214
1215 if (b instanceof jasmine.Matchers.ObjectContaining) {
1216 return b.matches(a);
1217 }
1218
1219 if (jasmine.isString_(a) && jasmine.isString_(b)) {
1220 return (a == b);
1221 }
1222
1223 if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
1224 return (a == b);
1225 }
1226
1227 if (a instanceof RegExp && b instanceof RegExp) {
1228 return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
1229 }
1230
1231 if (typeof a === "object" && typeof b === "object") {
1232 return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
1233 }
1234
1235 //Straight check
1236 return (a === b);
1237};
1238
1239jasmine.Env.prototype.contains_ = function(haystack, needle) {
1240 if (jasmine.isArray_(haystack)) {
1241 for (var i = 0; i < haystack.length; i++) {
1242 if (this.equals_(haystack[i], needle)) return true;
1243 }
1244 return false;
1245 }
1246 return haystack.indexOf(needle) >= 0;
1247};
1248
1249jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
1250 this.equalityTesters_.push(equalityTester);
1251};
1252/**
1253 * Basic browsers detection.
1254 */
1255jasmine.browser = {};
1256jasmine.browser.isIE = !!window.ActiveXObject;
1257jasmine.browser.isIE6 = jasmine.browser.isIE && !window.XMLHttpRequest;
1258jasmine.browser.isIE7 = jasmine.browser.isIE && !!window.XMLHttpRequest && !document.documentMode;
1259jasmine.browser.isIE8 = jasmine.browser.isIE && !!window.XMLHttpRequest && !!document.documentMode && !window.performance;
1260jasmine.browser.isIE9 = jasmine.browser.isIE && !!window.performance;
1261jasmine.browser.isSafari3 = /safari/.test(navigator.userAgent.toLowerCase()) && /version\/3/.test(navigator.userAgent.toLowerCase());
1262jasmine.browser.isOpera = !!window.opera;
1263jasmine.browser.isOpera11 = jasmine.browser.isOpera && parseInt(window.opera.version(), 10) > 10;
1264
1265jasmine.array = {};
1266
1267 /**
1268 * Checks whether or not the specified item exists in the array.
1269 * Array.prototype.indexOf is missing in Internet Explorer, unfortunately.
1270 * We always have to use this static method instead for consistency
1271 * @param {Array} array The array to check
1272 * @param {Mixed} item The item to look for
1273 * @param {Number} from (Optional) The index at which to begin the search
1274 * @return {Number} The index of item in the array (or -1 if it is not found)
1275 */
1276jasmine.array.indexOf = function(array, item, from){
1277 if (array.indexOf) {
1278 return array.indexOf(item, from);
1279 }
1280
1281 var i, length = array.length;
1282
1283 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
1284 if (array[i] === item) {
1285 return i;
1286 }
1287 }
1288
1289 return -1;
1290};
1291
1292 /**
1293 * Removes the specified item from the array. If the item is not found nothing happens.
1294 * @param {Array} array The array
1295 * @param {Mixed} item The item to remove
1296 * @return {Array} The passed array itself
1297 */
1298jasmine.array.remove = function(array, item) {
1299 var index = this.indexOf(array, item);
1300
1301 if (index !== -1) {
1302 array.splice(index, 1);
1303 }
1304
1305 return array;
1306};
1307
1308jasmine.Env.prototype.it = function(description, func, timeout) {
1309 var spec = new jasmine.Spec(this, this.currentSuite, description);
1310 this.currentSuite.add(spec);
1311 this.currentSpec = spec;
1312
1313 // override
1314 if (func) {
1315 func.typeName = 'it';
1316 var block = new jasmine.Block(spec.env, func, spec);
1317 block.timeout = parseInt(timeout);
1318 spec.addToQueue(block);
1319 }
1320 // end of override
1321
1322 return spec;
1323};
1324
1325jasmine.Env.prototype.specFor = function(object, specForFn) {
1326 var index = 0,
1327 property;
1328
1329 for (property in object) {
1330 if (!object.hasOwnProperty(property)) {
1331 continue;
1332 }
1333 specForFn.call(this, property, object[property], index, object);
1334 index = index + 1;
1335 }
1336};
1337
1338jasmine.Env.prototype.xspecFor = function(object, specForFn) {};/** No-op base class for Jasmine reporters.
1339 *
1340 * @constructor
1341 */
1342jasmine.Reporter = function() {
1343};
1344
1345//noinspection JSUnusedLocalSymbols
1346jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
1347};
1348
1349//noinspection JSUnusedLocalSymbols
1350jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
1351};
1352
1353//noinspection JSUnusedLocalSymbols
1354jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
1355};
1356
1357//noinspection JSUnusedLocalSymbols
1358jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
1359};
1360
1361//noinspection JSUnusedLocalSymbols
1362jasmine.Reporter.prototype.reportSpecResults = function(spec) {
1363};
1364
1365//noinspection JSUnusedLocalSymbols
1366jasmine.Reporter.prototype.log = function(str) {
1367};
1368
1369/**
1370 * Blocks are functions with executable code that make up a spec.
1371 *
1372 * @constructor
1373 * @param {jasmine.Env} env
1374 * @param {Function} func
1375 * @param {jasmine.Spec} spec
1376 */
1377jasmine.Block = function(env, func, spec) {
1378 this.env = env;
1379 this.func = func;
1380 this.spec = spec;
1381};
1382
1383jasmine.Block.prototype.execute = function(onComplete) {
1384 if (!jasmine.CATCH_EXCEPTIONS) {
1385 this.func.apply(this.spec);
1386 }
1387 else {
1388 try {
1389 this.func.apply(this.spec);
1390 } catch (e) {
1391 this.spec.fail(e);
1392 }
1393 }
1394 onComplete();
1395};
1396jasmine.Block.prototype.execute = function(onComplete) {
1397 if (this.func.length === 1) {
1398
1399 var timeOutId = setTimeout(function(){
1400 onComplete();
1401 }, this.timeout || jasmine.DEFAULT_TIMEOUT_INTERVAL);
1402
1403 if (!jasmine.CATCH_EXCEPTIONS) {
1404 this.func.call(this.spec, function() {
1405 clearTimeout(timeOutId);
1406 onComplete();
1407 });
1408 } else {
1409 try {
1410 this.func.call(this.spec, function() {
1411 clearTimeout(timeOutId);
1412 onComplete();
1413 });
1414 } catch (e) {
1415 this.spec.fail(e);
1416 onComplete();
1417 }
1418 }
1419 } else {
1420 if (!jasmine.CATCH_EXCEPTIONS) {
1421 this.func.apply(this.spec);
1422 } else {
1423 try {
1424 this.func.apply(this.spec);
1425 } catch (e) {
1426 this.spec.fail(e);
1427 }
1428 }
1429 onComplete();
1430 }
1431};/** JavaScript API reporter.
1432 *
1433 * @constructor
1434 */
1435jasmine.JsApiReporter = function() {
1436 this.started = false;
1437 this.finished = false;
1438 this.suites_ = [];
1439 this.results_ = {};
1440};
1441
1442jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
1443 this.started = true;
1444 var suites = runner.topLevelSuites();
1445 for (var i = 0; i < suites.length; i++) {
1446 var suite = suites[i];
1447 this.suites_.push(this.summarize_(suite));
1448 }
1449};
1450
1451jasmine.JsApiReporter.prototype.suites = function() {
1452 return this.suites_;
1453};
1454
1455jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
1456 var isSuite = suiteOrSpec instanceof jasmine.Suite;
1457 var summary = {
1458 id: suiteOrSpec.id,
1459 name: suiteOrSpec.description,
1460 type: isSuite ? 'suite' : 'spec',
1461 children: []
1462 };
1463
1464 if (isSuite) {
1465 var children = suiteOrSpec.children();
1466 for (var i = 0; i < children.length; i++) {
1467 summary.children.push(this.summarize_(children[i]));
1468 }
1469 }
1470 return summary;
1471};
1472
1473jasmine.JsApiReporter.prototype.results = function() {
1474 return this.results_;
1475};
1476
1477jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1478 return this.results_[specId];
1479};
1480
1481//noinspection JSUnusedLocalSymbols
1482jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1483 this.finished = true;
1484};
1485
1486//noinspection JSUnusedLocalSymbols
1487jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1488};
1489
1490//noinspection JSUnusedLocalSymbols
1491jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1492 this.results_[spec.id] = {
1493 messages: spec.results().getItems(),
1494 result: spec.results().failedCount > 0 ? "failed" : "passed"
1495 };
1496};
1497
1498//noinspection JSUnusedLocalSymbols
1499jasmine.JsApiReporter.prototype.log = function(str) {
1500};
1501
1502jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1503 var results = {};
1504 for (var i = 0; i < specIds.length; i++) {
1505 var specId = specIds[i];
1506 results[specId] = this.summarizeResult_(this.results_[specId]);
1507 }
1508 return results;
1509};
1510
1511jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1512 var summaryMessages = [];
1513 var messagesLength = result.messages.length;
1514 for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1515 var resultMessage = result.messages[messageIndex];
1516 summaryMessages.push({
1517 text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1518 passed: resultMessage.passed ? resultMessage.passed() : true,
1519 type: resultMessage.type,
1520 message: resultMessage.message,
1521 trace: {
1522 stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1523 }
1524 });
1525 }
1526
1527 return {
1528 result : result.result,
1529 messages : summaryMessages
1530 };
1531};
1532
1533/**
1534 * @constructor
1535 * @param {jasmine.Env} env
1536 * @param actual
1537 * @param {jasmine.Spec} spec
1538 */
1539jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1540 this.env = env;
1541 this.actual = actual;
1542 this.spec = spec;
1543 this.isNot = opt_isNot || false;
1544 this.reportWasCalled_ = false;
1545};
1546
1547// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1548jasmine.Matchers.pp = function(str) {
1549 throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1550};
1551
1552// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1553jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1554 throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1555};
1556
1557jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1558 for (var methodName in prototype) {
1559 if (methodName == 'report') continue;
1560 var orig = prototype[methodName];
1561 matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1562 }
1563};
1564
1565jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1566 return function() {
1567 var matcherArgs = jasmine.util.argsToArray(arguments);
1568 var result = matcherFunction.apply(this, arguments);
1569
1570 if (this.isNot) {
1571 result = !result;
1572 }
1573
1574 if (this.reportWasCalled_) return result;
1575
1576 var message;
1577 if (!result) {
1578 if (this.message) {
1579 message = this.message.apply(this, arguments);
1580 if (jasmine.isArray_(message)) {
1581 message = message[this.isNot ? 1 : 0];
1582 }
1583 } else {
1584 var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1585 message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1586 if (matcherArgs.length > 0) {
1587 for (var i = 0; i < matcherArgs.length; i++) {
1588 if (i > 0) message += ",";
1589 message += " " + jasmine.pp(matcherArgs[i]);
1590 }
1591 }
1592 message += ".";
1593 }
1594 }
1595 var expectationResult = new jasmine.ExpectationResult({
1596 matcherName: matcherName,
1597 passed: result,
1598 expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1599 actual: this.actual,
1600 message: message
1601 });
1602 this.spec.addMatcherResult(expectationResult);
1603 return jasmine.undefined;
1604 };
1605};
1606
1607
1608
1609
1610/**
1611 * toBe: compares the actual to the expected using ===
1612 * @param expected
1613 */
1614jasmine.Matchers.prototype.toBe = function(expected) {
1615 return this.actual === expected;
1616};
1617
1618/**
1619 * toNotBe: compares the actual to the expected using !==
1620 * @param expected
1621 * @deprecated as of 1.0. Use not.toBe() instead.
1622 */
1623jasmine.Matchers.prototype.toNotBe = function(expected) {
1624 return this.actual !== expected;
1625};
1626
1627/**
1628 * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1629 *
1630 * @param expected
1631 */
1632jasmine.Matchers.prototype.toEqual = function(expected) {
1633 return this.env.equals_(this.actual, expected);
1634};
1635
1636/**
1637 * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1638 * @param expected
1639 * @deprecated as of 1.0. Use not.toEqual() instead.
1640 */
1641jasmine.Matchers.prototype.toNotEqual = function(expected) {
1642 return !this.env.equals_(this.actual, expected);
1643};
1644
1645/**
1646 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
1647 * a pattern or a String.
1648 *
1649 * @param expected
1650 */
1651jasmine.Matchers.prototype.toMatch = function(expected) {
1652 return new RegExp(expected).test(this.actual);
1653};
1654
1655/**
1656 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1657 * @param expected
1658 * @deprecated as of 1.0. Use not.toMatch() instead.
1659 */
1660jasmine.Matchers.prototype.toNotMatch = function(expected) {
1661 return !(new RegExp(expected).test(this.actual));
1662};
1663
1664/**
1665 * Matcher that compares the actual to jasmine.undefined.
1666 */
1667jasmine.Matchers.prototype.toBeDefined = function() {
1668 return (this.actual !== jasmine.undefined);
1669};
1670
1671/**
1672 * Matcher that compares the actual to jasmine.undefined.
1673 */
1674jasmine.Matchers.prototype.toBeUndefined = function() {
1675 return (this.actual === jasmine.undefined);
1676};
1677
1678/**
1679 * Matcher that compares the actual to null.
1680 */
1681jasmine.Matchers.prototype.toBeNull = function() {
1682 return (this.actual === null);
1683};
1684
1685/**
1686 * Matcher that compares the actual to NaN.
1687 */
1688jasmine.Matchers.prototype.toBeNaN = function() {
1689 this.message = function() {
1690 return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
1691 };
1692
1693 return (this.actual !== this.actual);
1694};
1695
1696/**
1697 * Matcher that boolean not-nots the actual.
1698 */
1699jasmine.Matchers.prototype.toBeTruthy = function() {
1700 return !!this.actual;
1701};
1702
1703
1704/**
1705 * Matcher that boolean nots the actual.
1706 */
1707jasmine.Matchers.prototype.toBeFalsy = function() {
1708 return !this.actual;
1709};
1710
1711
1712/**
1713 * Matcher that checks to see if the actual, a Jasmine spy, was called.
1714 */
1715jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1716 if (arguments.length > 0) {
1717 throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1718 }
1719
1720 if (!jasmine.isSpy(this.actual)) {
1721 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1722 }
1723
1724 this.message = function() {
1725 return [
1726 "Expected spy " + this.actual.identity + " to have been called.",
1727 "Expected spy " + this.actual.identity + " not to have been called."
1728 ];
1729 };
1730
1731 return this.actual.wasCalled;
1732};
1733
1734/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1735jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1736
1737/**
1738 * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1739 *
1740 * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1741 */
1742jasmine.Matchers.prototype.wasNotCalled = function() {
1743 if (arguments.length > 0) {
1744 throw new Error('wasNotCalled does not take arguments');
1745 }
1746
1747 if (!jasmine.isSpy(this.actual)) {
1748 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1749 }
1750
1751 this.message = function() {
1752 return [
1753 "Expected spy " + this.actual.identity + " to not have been called.",
1754 "Expected spy " + this.actual.identity + " to have been called."
1755 ];
1756 };
1757
1758 return !this.actual.wasCalled;
1759};
1760
1761/**
1762 * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1763 *
1764 * @example
1765 *
1766 */
1767jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1768 var expectedArgs = jasmine.util.argsToArray(arguments);
1769 if (!jasmine.isSpy(this.actual)) {
1770 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1771 }
1772 this.message = function() {
1773 var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
1774 var positiveMessage = "";
1775 if (this.actual.callCount === 0) {
1776 positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
1777 } else {
1778 positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
1779 }
1780 return [positiveMessage, invertedMessage];
1781 };
1782
1783 return this.env.contains_(this.actual.argsForCall, expectedArgs);
1784};
1785
1786/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1787jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1788
1789/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1790jasmine.Matchers.prototype.wasNotCalledWith = function() {
1791 var expectedArgs = jasmine.util.argsToArray(arguments);
1792 if (!jasmine.isSpy(this.actual)) {
1793 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1794 }
1795
1796 this.message = function() {
1797 return [
1798 "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1799 "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1800 ];
1801 };
1802
1803 return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1804};
1805
1806/**
1807 * Matcher that checks that the expected item is an element in the actual Array.
1808 *
1809 * @param {Object} expected
1810 */
1811jasmine.Matchers.prototype.toContain = function(expected) {
1812 return this.env.contains_(this.actual, expected);
1813};
1814
1815/**
1816 * Matcher that checks that the expected item is NOT an element in the actual Array.
1817 *
1818 * @param {Object} expected
1819 * @deprecated as of 1.0. Use not.toContain() instead.
1820 */
1821jasmine.Matchers.prototype.toNotContain = function(expected) {
1822 return !this.env.contains_(this.actual, expected);
1823};
1824
1825jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1826 return this.actual < expected;
1827};
1828
1829jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1830 return this.actual > expected;
1831};
1832
1833/**
1834 * Matcher that checks that the expected item is equal to the actual item
1835 * up to a given level of decimal precision (default 2).
1836 *
1837 * @param {Number} expected
1838 * @param {Number} precision, as number of decimal places
1839 */
1840jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
1841 if (!(precision === 0)) {
1842 precision = precision || 2;
1843 }
1844 return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
1845};
1846
1847/**
1848 * Matcher that checks that the expected exception was thrown by the actual.
1849 *
1850 * @param {String} [expected]
1851 */
1852jasmine.Matchers.prototype.toThrow = function(expected) {
1853 var result = false;
1854 var exception;
1855 if (typeof this.actual != 'function') {
1856 throw new Error('Actual is not a function');
1857 }
1858 try {
1859 this.actual();
1860 } catch (e) {
1861 exception = e;
1862 }
1863 if (exception) {
1864 result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1865 }
1866
1867 var not = this.isNot ? "not " : "";
1868
1869 this.message = function() {
1870 if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1871 return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
1872 } else {
1873 return "Expected function to throw an exception.";
1874 }
1875 };
1876
1877 return result;
1878};
1879
1880jasmine.Matchers.Any = function(expectedClass) {
1881 this.expectedClass = expectedClass;
1882};
1883
1884jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
1885 if (this.expectedClass == String) {
1886 return typeof other == 'string' || other instanceof String;
1887 }
1888
1889 if (this.expectedClass == Number) {
1890 return typeof other == 'number' || other instanceof Number;
1891 }
1892
1893 if (this.expectedClass == Function) {
1894 return typeof other == 'function' || other instanceof Function;
1895 }
1896
1897 if (this.expectedClass == Object) {
1898 return typeof other == 'object';
1899 }
1900
1901 return other instanceof this.expectedClass;
1902};
1903
1904jasmine.Matchers.Any.prototype.jasmineToString = function() {
1905 return '<jasmine.any(' + this.expectedClass + ')>';
1906};
1907
1908jasmine.Matchers.ObjectContaining = function (sample) {
1909 this.sample = sample;
1910};
1911
1912jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
1913 mismatchKeys = mismatchKeys || [];
1914 mismatchValues = mismatchValues || [];
1915
1916 var env = jasmine.getEnv();
1917
1918 var hasKey = function(obj, keyName) {
1919 return obj != null && obj[keyName] !== jasmine.undefined;
1920 };
1921
1922 for (var property in this.sample) {
1923 if (!hasKey(other, property) && hasKey(this.sample, property)) {
1924 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1925 }
1926 else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
1927 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.");
1928 }
1929 }
1930
1931 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1932};
1933
1934jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
1935 return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
1936};
1937/**
1938 * Override of toThrow special opera 10 !!!
1939 */
1940jasmine.Matchers.prototype.toThrow = function(expected) {
1941 var result = false;
1942 var exception;
1943 if (typeof this.actual != 'function') {
1944 throw new Error('Actual is not a function');
1945 }
1946
1947 // mock the console to avoid logging to the real console during the tests
1948 var global = Ext.global;
1949
1950 Ext.global = {
1951 console: {
1952 dir: function(s) {
1953 return s;
1954 },
1955 log: function(s) {
1956 return s;
1957 },
1958 error: function(s) {
1959 return s;
1960 },
1961 warn: function(s) {
1962 return s;
1963 }
1964 }
1965 };
1966
1967 // This is to allow setting breakpoints for console messages
1968 // that are not expected to be suppressed by jasmine.toThrow and alike
1969 Ext.global.console.dir.$emptyFn = Ext.global.console.log.$emptyFn = true;
1970 Ext.global.console.error.$emptyFn = Ext.global.console.warn.$emptyFn = true;
1971
1972 try {
1973 this.actual();
1974 } catch (e) {
1975 exception = e;
1976 }
1977 if (exception) {
1978 result = (expected === jasmine.undefined || this.env.contains_(exception.message || exception, expected.message || expected));
1979 }
1980
1981 Ext.global = global;
1982
1983 var not = this.isNot ? "not " : "";
1984
1985 this.message = function() {
1986 if (exception && (expected === jasmine.undefined || !this.env.contains_(exception.message || exception, expected.message || expected))) {
1987 return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
1988 } else {
1989 return "Expected function to throw an exception.";
1990 }
1991 };
1992
1993 return result;
1994};
1995
1996jasmine.Matchers.prototype.toRaiseExtError = function(expected) {
1997 var result = false,
1998 global = Ext.global,
1999 extError;
2000 if (typeof this.actual != 'function') {
2001 throw new Error('Actual is not a function');
2002 }
2003
2004 // mock the console to avoid logging to the real console during the tests
2005 Ext.global = {
2006 console: {
2007 dir: function(s) {
2008 return s;
2009 },
2010 log: function(s) {
2011 return s;
2012 },
2013 error: function(s) {
2014 return s;
2015 },
2016 warn: function(s) {
2017 return s;
2018 }
2019 }
2020 };
2021
2022 // This is to allow setting breakpoints for console messages
2023 // that are not expected to be suppressed by jasmine.toThrow and alike
2024 Ext.global.console.dir.$emptyFn = Ext.global.console.log.$emptyFn = true;
2025 Ext.global.console.error.$emptyFn = Ext.global.console.warn.$emptyFn = true;
2026
2027 try {
2028 this.actual();
2029 } catch (e) {
2030 extError = e;
2031 }
2032
2033
2034 Ext.global = global;
2035
2036 if (extError && extError instanceof Error) {
2037 result = (expected === jasmine.undefined || this.env.contains_(extError.toString(), expected.message || expected));
2038 }
2039
2040
2041 var not = this.isNot ? "not " : "";
2042
2043 this.message = function() {
2044 if (!extError instanceof Error) {
2045 return "Exception thrown is not an instance of Ext.Error";
2046 } else if (extError && (expected === jasmine.undefined || !this.env.contains_(extError.toString(), expected.message || expected))) {
2047 return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", extError.toString()].join(' ');
2048 } else {
2049 return "Expected function to throw an exception.";
2050 }
2051 };
2052
2053 return result;
2054};
2055
2056jasmine.Matchers.prototype.hasHTML = function(expected) {
2057 var me = this;
2058
2059 if (!me.actual || !me.actual.tagName) {
2060 throw new Error('Actual is not a dom element');
2061 }
2062 if (jasmine.browser.isSafari3) {
2063 expected = expected.replace(/&gt;/g, '>');
2064 }
2065 // this normalize innerHTML which could vary a lot
2066 var normalizedHTML = me.actual.innerHTML.replace(/<[^>]*>/g, function(match1) {
2067 return match1.toLowerCase().replace(/=\w+/g, function(match2) {
2068 return '="' + match2.split('=')[1] + '"';
2069 });
2070 });
2071
2072 me.message = function() {
2073 return [
2074 "Expected dom element innerHTML to be " + expected + " but was " + normalizedHTML,
2075 "Expected dom element innerHTML to not be " + expected + "."
2076 ];
2077 };
2078
2079 return normalizedHTML === expected;
2080};
2081
2082jasmine.Matchers.prototype.toHaveCls = function(cls) {
2083 return Ext.fly(this.actual).hasCls(cls);
2084};
2085
2086jasmine.Matchers.prototype.toEqualTime = function(hour, minute, second, ms) {
2087 var actual = this.actual;
2088 return actual instanceof Date &&
2089 actual.getHours() === hour &&
2090 actual.getMinutes() === (minute || 0) &&
2091 actual.getSeconds() === (second || 0) &&
2092 actual.getMilliseconds() === (ms || 0);
2093
2094};
2095
2096jasmine.Matchers.prototype.toBePositionedAt = function(x, y) {
2097 var xy = this.actual.getXY();
2098 this.message = function() {
2099 return "Expected Ext.Element to be positioned at (" + x + "," + y + ") but was positioned at (" + xy[0] + "," + xy[1] + ")";
2100 };
2101 return xy[0] === x && xy[1] === y;
2102};
2103
2104(function () {
2105 var elementPropGetters = {
2106 x: function (el, root) {
2107 var x = el.getX(),
2108 x0 = root ? root.el.getX() : el.getX();
2109 return x - x0;
2110 },
2111 y: function (el, root) {
2112 var y = el.getY(),
2113 y0 = root ? root.el.getY() : el.getY();
2114 return y - y0;
2115 },
2116 w: function (el) {
2117 return el.getWidth();
2118 },
2119 h: function (el) {
2120 return el.getHeight();
2121 },
2122 xywh: function(el, root) {
2123 var x= el.getX(),
2124 x0 = root ? root.el.getX() : el.getX(),
2125 y = el.getY(),
2126 y0 = root ? root.el.getY() : el.getY(),
2127 w = el.getWidth(),
2128 h = el.getHeight(),
2129 dims = [];
2130 dims.push(x - x0, y - y0, w, h);
2131 return dims.join(' ');
2132 },
2133 cls: function (el) {
2134 return el.dom.className;
2135 }
2136 },
2137 browsers = [
2138 "IE6", "IE7", "IE8", "IE9", "IE",
2139 "Gecko3", "Gecko4", "Gecko5", "Gecko10", "Gecko",
2140 "FF3_6", "FF4", "FF5",
2141 "Chrome",
2142 "Safari2", "Safari3", "Safari4", "Safari5", "Safari"
2143 ],
2144 blen = browsers.length,
2145 b, browser,
2146 browserCheck = function(expected){
2147 if(Ext.isNumeric(expected) || Ext.isArray(expected)) {
2148 return expected;
2149 }
2150 for (b = 0; b < blen; b++) {
2151 browser = browsers[b];
2152 if (expected.hasOwnProperty(browser) && Ext["is" + browser]){
2153 return expected[browser];
2154 }
2155 }
2156 return expected['*'] || expected;
2157 },
2158 layoutFly = new Ext.dom.Fly();
2159
2160
2161 function checkLayout (comp, layout, root, path) {
2162 Ext.Object.each(layout, function (name, value) {
2163 if (name == 'items' || name == 'dockedItems') {
2164 Ext.Object.each(value, function (id, sub) {
2165 var isNum = String(parseInt(id,10)) == id,
2166 child = isNum ? comp[name].items[parseInt(id,10)]
2167 : (comp.getComponent(id) || comp.child(id));
2168
2169 if (isNum) {
2170 id = '.' + name + '[' + id + ']';
2171 } else if (id.charAt(0) != ':') {
2172 id = '_' + id;
2173 }
2174
2175 if (child) {
2176 checkLayout(child, sub, comp, path + id);
2177 } else {
2178 expect(id).toBe('found!');
2179 }
2180 });
2181 } else {
2182 // the name is an element name like 'body'
2183 var el = comp[name];
2184
2185 if (!el) {
2186 // no child el matched, assume the key is a CSS selector
2187 el = layoutFly.attach(comp.el.selectNode(name, true));
2188 }
2189
2190 if (el.isComponent) {
2191 checkLayout(el, value, el.ownerCt, path + '_' + name);
2192 } else if (el.dom) {
2193 value = browserCheck(value);
2194 if (value.xywh) {
2195 var dims = value.xywh.split(' ');
2196 value.x = eval('(' + dims[0] + ')');
2197 value.y = eval('(' + dims[1] + ')');
2198 value.w = eval('(' + dims[2] + ')');
2199 value.h = eval('(' + dims[3] + ')');
2200 delete value.xywh;
2201 }
2202 Ext.Object.each(value, function (prop, expected) {
2203 var actual = elementPropGetters[prop](el, root || comp.el),
2204 pfx = (path ? path + '.' : '') + name + '.' + prop + '=';
2205
2206 if (Ext.isArray(expected)) {
2207 if (actual < expected[0] || actual > expected[1]) {
2208 expect(pfx + '=' + actual).
2209 toBe('in [' + expected[0] + ',' + expected[1] + ']');
2210 }
2211 } else if (actual != expected) {
2212 expect(pfx + actual).toEqual(expected);
2213 }
2214 });
2215 }
2216 }
2217 });
2218 }
2219
2220 jasmine.Matchers.prototype.toHaveLayout = function(expected) {
2221 var comp = this.actual;
2222 checkLayout(comp, expected, comp.ownerCt, comp.getXType());
2223 return true;
2224 };
2225
2226 jasmine.Matchers.prototype.toBeLessThanOrEqual = function(expected) {
2227 return this.actual <= expected;
2228 };
2229
2230 jasmine.Matchers.prototype.toBeGreaterThanOrEqual = function(expected) {
2231 return this.actual >= expected;
2232 };
2233
2234 jasmine.Matchers.prototype.toBeGE = jasmine.Matchers.prototype.toBeAtLeast = jasmine.Matchers.prototype.toBeGreaterThanOrEqual;
2235 jasmine.Matchers.prototype.toBeLE = jasmine.Matchers.prototype.toBeLessThanOrEqual;
2236 jasmine.Matchers.prototype.toBeLT = jasmine.Matchers.prototype.toBeLessThan;
2237 jasmine.Matchers.prototype.toBeGT = jasmine.Matchers.prototype.toBeGreaterThan;
2238})();
2239
2240
2241 jasmine.Matchers.prototype.toHaveFiredEvents = function() {
2242 var calls = this.actual.fireEvent.calls,
2243 i = 0,
2244 ret = true,
2245 expectedEvents = Array.prototype.slice.call(arguments, 0),
2246 length = expectedEvents.length,
2247 actualEvents = [],
2248 actualEvent,
2249 expectedEvent;
2250
2251
2252
2253 for (;i < length; i++) {
2254 expectedEvent = expectedEvents[i];
2255 try {
2256 actualEvent = calls[i].args[0];
2257 } catch (e) {
2258 actualEvent = null;
2259 }
2260 if (actualEvent) {
2261 actualEvents.push(actualEvent);
2262 }
2263
2264 if (actualEvent != expectedEvent) {
2265 ret = false;
2266 }
2267 }
2268
2269 this.message = function() {
2270 return "Expected events flow to be (" + expectedEvents.length + " events): \n" + expectedEvents.join('\n') + "\nBut it was (" + actualEvents.length + " events): \n"+ actualEvents.join('\n');
2271 };
2272 return ret;
2273 };
2274
2275jasmine.Matchers.prototype.toBeApprox = function(expected, errorMargin) {
2276 errorMargin = errorMargin || 1;
2277
2278 var min = expected - errorMargin,
2279 max = expected + errorMargin;
2280
2281 this.message = function() {
2282 return "Expected " + this.actual + " to be approximately " + expected + " by " + errorMargin;
2283 }
2284 return this.actual >= min && this.actual <= max;
2285};
2286
2287jasmine.Matchers.prototype.toBeWithin = function(deviation, value) {
2288 var actual = this.actual;
2289
2290 if (deviation > 0) {
2291 return actual >= (value - deviation) && actual <= (value + deviation);
2292 }
2293 else {
2294 return actual >= (value + deviation) && actual <= (value - deviation);
2295 }
2296};
2297/**
2298 * @constructor
2299 */
2300jasmine.MultiReporter = function() {
2301 this.subReporters_ = [];
2302};
2303jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
2304
2305jasmine.MultiReporter.prototype.addReporter = function(reporter) {
2306 this.subReporters_.push(reporter);
2307};
2308
2309(function() {
2310 var functionNames = [
2311 "reportRunnerStarting",
2312 "reportRunnerResults",
2313 "reportSuiteStarting",
2314 "reportSuiteResults",
2315 "reportSpecStarting",
2316 "reportSpecResults",
2317 "log"
2318 ];
2319 for (var i = 0; i < functionNames.length; i++) {
2320 var functionName = functionNames[i];
2321 jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
2322 return function() {
2323 for (var j = 0; j < this.subReporters_.length; j++) {
2324 var subReporter = this.subReporters_[j];
2325 if (subReporter[functionName]) {
2326 subReporter[functionName].apply(subReporter, arguments);
2327 }
2328 }
2329 };
2330 })(functionName);
2331 }
2332})();/**
2333 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
2334 *
2335 * @constructor
2336 */
2337jasmine.NestedResults = function() {
2338 /**
2339 * The total count of results
2340 */
2341 this.totalCount = 0;
2342 /**
2343 * Number of passed results
2344 */
2345 this.passedCount = 0;
2346 /**
2347 * Number of failed results
2348 */
2349 this.failedCount = 0;
2350 /**
2351 * Was this suite/spec skipped?
2352 */
2353 this.skipped = false;
2354 /**
2355 * @ignore
2356 */
2357 this.items_ = [];
2358};
2359
2360/**
2361 * Roll up the result counts.
2362 *
2363 * @param result
2364 */
2365jasmine.NestedResults.prototype.rollupCounts = function(result) {
2366 this.totalCount += result.totalCount;
2367 this.passedCount += result.passedCount;
2368 this.failedCount += result.failedCount;
2369};
2370
2371/**
2372 * Adds a log message.
2373 * @param values Array of message parts which will be concatenated later.
2374 */
2375jasmine.NestedResults.prototype.log = function(values) {
2376 this.items_.push(new jasmine.MessageResult(values));
2377};
2378
2379/**
2380 * Getter for the results: message & results.
2381 */
2382jasmine.NestedResults.prototype.getItems = function() {
2383 return this.items_;
2384};
2385
2386/**
2387 * Adds a result, tracking counts (total, passed, & failed)
2388 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
2389 */
2390jasmine.NestedResults.prototype.addResult = function(result) {
2391 if (result.type != 'log') {
2392 if (result.items_) {
2393 this.rollupCounts(result);
2394 } else {
2395 this.totalCount++;
2396 if (result.passed()) {
2397 this.passedCount++;
2398 } else {
2399 this.failedCount++;
2400 }
2401 }
2402 }
2403 this.items_.push(result);
2404};
2405
2406/**
2407 * @returns {Boolean} True if <b>everything</b> below passed
2408 */
2409jasmine.NestedResults.prototype.passed = function() {
2410 return this.passedCount === this.totalCount;
2411};
2412
2413jasmine.NestedResults.prototype.cleanup = function() {
2414 this.items_ = null;
2415};
2416/**
2417 * Base class for pretty printing for expectation results.
2418 */
2419jasmine.PrettyPrinter = function() {
2420 this.ppNestLevel_ = 0;
2421};
2422
2423/**
2424 * Formats a value in a nice, human-readable string.
2425 *
2426 * @param value
2427 */
2428jasmine.PrettyPrinter.prototype.format = function(value) {
2429 this.ppNestLevel_++;
2430 try {
2431 if (value === jasmine.undefined) {
2432 this.emitScalar('undefined');
2433 } else if (value === null) {
2434 this.emitScalar('null');
2435 } else if (value === jasmine.getGlobal()) {
2436 this.emitScalar('<global>');
2437 } else if (value.jasmineToString) {
2438 this.emitScalar(value.jasmineToString());
2439 } else if (typeof value === 'string') {
2440 this.emitString(value);
2441 } else if (jasmine.isSpy(value)) {
2442 this.emitScalar("spy on " + value.identity);
2443 } else if (value instanceof RegExp) {
2444 this.emitScalar(value.toString());
2445 } else if (typeof value === 'function') {
2446 this.emitScalar('Function');
2447 } else if (typeof value.nodeType === 'number') {
2448 this.emitScalar('HTMLNode');
2449 } else if (value instanceof Date) {
2450 this.emitScalar('Date(' + value + ')');
2451 } else if (value.__Jasmine_been_here_before__) {
2452 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
2453 } else if (jasmine.isArray_(value) || typeof value == 'object') {
2454 value.__Jasmine_been_here_before__ = true;
2455 if (jasmine.isArray_(value)) {
2456 this.emitArray(value);
2457 } else {
2458 this.emitObject(value);
2459 }
2460 delete value.__Jasmine_been_here_before__;
2461 } else {
2462 this.emitScalar(value.toString());
2463 }
2464 } finally {
2465 this.ppNestLevel_--;
2466 }
2467};
2468
2469jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
2470 for (var property in obj) {
2471 if (!obj.hasOwnProperty(property)) continue;
2472 if (property == '__Jasmine_been_here_before__') continue;
2473 fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
2474 obj.__lookupGetter__(property) !== null) : false);
2475 }
2476};
2477
2478jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
2479jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
2480jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
2481jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
2482
2483jasmine.StringPrettyPrinter = function() {
2484 jasmine.PrettyPrinter.call(this);
2485
2486 this.string = '';
2487};
2488jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
2489
2490jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
2491 this.append(value);
2492};
2493
2494jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
2495 this.append("'" + value + "'");
2496};
2497
2498jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
2499 if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
2500 this.append("Array");
2501 return;
2502 }
2503
2504 this.append('[ ');
2505 for (var i = 0; i < array.length; i++) {
2506 if (i > 0) {
2507 this.append(', ');
2508 }
2509 this.format(array[i]);
2510 }
2511 this.append(' ]');
2512};
2513
2514jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
2515 if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
2516 this.append("Object");
2517 return;
2518 }
2519
2520 var self = this;
2521 this.append('{ ');
2522 var first = true;
2523
2524 this.iterateObject(obj, function(property, isGetter) {
2525 if (first) {
2526 first = false;
2527 } else {
2528 self.append(', ');
2529 }
2530
2531 self.append(property);
2532 self.append(' : ');
2533 if (isGetter) {
2534 self.append('<getter>');
2535 } else {
2536 self.format(obj[property]);
2537 }
2538 });
2539
2540 this.append(' }');
2541};
2542
2543jasmine.StringPrettyPrinter.prototype.append = function(value) {
2544 this.string += value;
2545};
2546(function() {
2547 var prototype = jasmine.PrettyPrinter.prototype,
2548 superFormat = prototype.format;
2549
2550 prototype.format = function(value) {
2551 var className, superclass;
2552
2553 if (value) {
2554 className = value.$className;
2555
2556 if (className !== undefined) {
2557 // support for pretty printing instances of Ext classes
2558
2559 if (!className) {
2560 // support for anonymous classes - Ext.define(null, ...)
2561 // loop up the inheritance chain to find nearest non-anonymous ancestor
2562 superclass = value.superclass;
2563 while (superclass && !superclass.$className) {
2564 superclass = superclass.superclass;
2565 }
2566 if (superclass) {
2567 className = superclass.$className;
2568 }
2569 }
2570 this.emitScalar(className + '#' + (value.id || (value.getId && value.getId())));
2571 return;
2572 }
2573 }
2574
2575 superFormat.call(this, value);
2576 };
2577})();jasmine.Queue = function(env) {
2578 this.env = env;
2579
2580 // parallel to blocks. each true value in this array means the block will
2581 // get executed even if we abort
2582 this.ensured = [];
2583 this.blocks = [];
2584 this.running = false;
2585 this.index = 0;
2586 this.offset = 0;
2587 this.abort = false;
2588};
2589
2590jasmine.Queue.prototype.addBefore = function(block, ensure) {
2591 if (ensure === jasmine.undefined) {
2592 ensure = false;
2593 }
2594
2595 this.blocks.unshift(block);
2596 this.ensured.unshift(ensure);
2597};
2598
2599jasmine.Queue.prototype.add = function(block, ensure) {
2600 if (ensure === jasmine.undefined) {
2601 ensure = false;
2602 }
2603
2604 this.blocks.push(block);
2605 this.ensured.push(ensure);
2606};
2607
2608jasmine.Queue.prototype.insertNext = function(block, ensure) {
2609 if (ensure === jasmine.undefined) {
2610 ensure = false;
2611 }
2612
2613 this.ensured.splice((this.index + this.offset + 1), 0, ensure);
2614 this.blocks.splice((this.index + this.offset + 1), 0, block);
2615 this.offset++;
2616};
2617
2618jasmine.Queue.prototype.start = function(onComplete) {
2619 this.running = true;
2620 this.onComplete = onComplete;
2621 this.next_();
2622};
2623
2624jasmine.Queue.prototype.isRunning = function() {
2625 return this.running;
2626};
2627
2628jasmine.Queue.LOOP_DONT_RECURSE = true;
2629
2630jasmine.Queue.prototype.next_ = function() {
2631 var self = this;
2632 var goAgain = true;
2633
2634 while (goAgain) {
2635 goAgain = false;
2636
2637 if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
2638 var calledSynchronously = true;
2639 var completedSynchronously = false;
2640
2641 var onComplete = function () {
2642 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
2643 completedSynchronously = true;
2644 return;
2645 }
2646
2647 if (self.blocks[self.index].abort) {
2648 self.abort = true;
2649 }
2650
2651 self.offset = 0;
2652 self.index++;
2653
2654 var now = new Date().getTime();
2655 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
2656 self.env.lastUpdate = now;
2657 self.env.setTimeout(function() {
2658 self.next_();
2659 }, 0);
2660 } else {
2661 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
2662 goAgain = true;
2663 } else {
2664 self.next_();
2665 }
2666 }
2667 };
2668 self.blocks[self.index].execute(onComplete);
2669
2670 calledSynchronously = false;
2671 if (completedSynchronously) {
2672 onComplete();
2673 }
2674
2675 } else {
2676 self.running = false;
2677 if (self.onComplete) {
2678 self.onComplete();
2679 }
2680 }
2681 }
2682};
2683
2684jasmine.Queue.prototype.results = function() {
2685 var results = new jasmine.NestedResults();
2686 for (var i = 0; i < this.blocks.length; i++) {
2687 if (this.blocks[i].results) {
2688 results.addResult(this.blocks[i].results());
2689 }
2690 }
2691 return results;
2692};
2693
2694
2695jasmine.Queue.prototype.next_ = function() {
2696 var self = this;
2697 var goAgain = true;
2698
2699 while (goAgain) {
2700 goAgain = false;
2701
2702 if (self.index < self.blocks.length) {
2703 var calledSynchronously = true;
2704 var completedSynchronously = false;
2705
2706 var onComplete = function() {
2707 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
2708 completedSynchronously = true;
2709 return;
2710 }
2711
2712 if (self.blocks[self.index].abort) {
2713 self.abort = true;
2714 }
2715
2716 self.offset = 0;
2717 self.index++;
2718
2719 var now = new Date().getTime();
2720
2721 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
2722 self.env.lastUpdate = now;
2723 self.env.setTimeout(function() {
2724 self.next_();
2725 }, 0);
2726 }
2727 else {
2728 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
2729 goAgain = true;
2730 }
2731 else {
2732 self.next_();
2733 }
2734 }
2735 };
2736
2737 if (!this.abort || this.ensured[self.index]) {
2738 self.blocks[self.index].execute(onComplete);
2739 }
2740 else {
2741 onComplete();
2742 }
2743
2744 calledSynchronously = false;
2745
2746 if (completedSynchronously) {
2747 onComplete();
2748 }
2749 }
2750 else {
2751 self.running = false;
2752
2753 if (self.onComplete) {
2754 self.onComplete();
2755 }
2756
2757 self.finish();
2758 }
2759 }
2760};
2761
2762jasmine.Queue.prototype.finish = function() {
2763 var me = this,
2764 blocks = me.blocks,
2765 i, len, block;
2766
2767 // Block functions are closures that keep a lot of test objects retained.
2768 // We don't want all this cruft hanging around when the queue is finished.
2769 for (i = 0, len = blocks.length; i < len; i++) {
2770 block = blocks[i];
2771
2772 block.func = null;
2773
2774 if (block.latchFunction) {
2775 block.latchFunction = null;
2776 }
2777
2778 if (block.timeout) {
2779 block.timeout = null;
2780 }
2781 }
2782
2783 me.finished = true;
2784}
2785/**
2786 * Runner
2787 *
2788 * @constructor
2789 * @param {jasmine.Env} env
2790 */
2791jasmine.Runner = function(env) {
2792 var self = this;
2793 self.env = env;
2794 self.queue = new jasmine.Queue(env);
2795 self.before_ = [];
2796 self.after_ = [];
2797 self.suites_ = [];
2798};
2799
2800jasmine.Runner.prototype.execute = function() {
2801 var self = this;
2802 if (self.env.reporter.reportRunnerStarting) {
2803 self.env.reporter.reportRunnerStarting(this);
2804 }
2805 self.queue.start(function () {
2806 self.finishCallback();
2807 });
2808};
2809
2810jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
2811 beforeEachFunction.typeName = 'beforeEach';
2812 this.before_.splice(0,0,beforeEachFunction);
2813};
2814
2815jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
2816 afterEachFunction.typeName = 'afterEach';
2817 this.after_.splice(0,0,afterEachFunction);
2818};
2819
2820
2821jasmine.Runner.prototype.finishCallback = function() {
2822 this.env.reporter.reportRunnerResults(this);
2823};
2824
2825jasmine.Runner.prototype.addSuite = function(suite) {
2826 this.suites_.push(suite);
2827};
2828
2829jasmine.Runner.prototype.add = function(block) {
2830 if (block instanceof jasmine.Suite) {
2831 this.addSuite(block);
2832 }
2833 this.queue.add(block);
2834};
2835
2836jasmine.Runner.prototype.specs = function () {
2837 var suites = this.suites();
2838 var specs = [];
2839 for (var i = 0; i < suites.length; i++) {
2840 specs.push.apply(specs, suites[i].specs());
2841 }
2842 return specs;
2843};
2844
2845jasmine.Runner.prototype.suites = function() {
2846 return this.suites_;
2847};
2848
2849jasmine.Runner.prototype.topLevelSuites = function() {
2850 var topLevelSuites = [];
2851 for (var i = 0; i < this.suites_.length; i++) {
2852 if (!this.suites_[i].parentSuite) {
2853 topLevelSuites.push(this.suites_[i]);
2854 }
2855 }
2856 return topLevelSuites;
2857};
2858
2859jasmine.Runner.prototype.results = function() {
2860 return this.queue.results();
2861};
2862jasmine.Runner.prototype.filter = function(suiteIds, specIds) {
2863 // convert [1, 2] into { 1: true, 2: true }
2864 //
2865 if (typeof suiteIds.length == 'number') {
2866 suiteIds = jasmine.toMap(suiteIds);
2867 }
2868 if (typeof specIds.length == 'number') {
2869 specIds = jasmine.toMap(specIds);
2870 }
2871
2872 var specs = jasmine.getByIds(this.specs(), specIds),
2873 suites = jasmine.getByIds(this.suites(), suiteIds),
2874 blocks = [],
2875 i, length, suite;
2876
2877 length = specs.length;
2878 for (i = 0; i < length; i++) {
2879 suite = specs[i].getRootSuite();
2880 if (jasmine.array.indexOf(blocks, suite) === -1) {
2881 suite.filter(suiteIds, specIds);
2882 blocks.push(suite);
2883 }
2884 }
2885
2886 length = suites.length;
2887 for (i = 0; i < length; i++) {
2888 suite = suites[i].getRootSuite();
2889 if (jasmine.array.indexOf(blocks, suite) === -1) {
2890 suite.filter(suiteIds, specIds);
2891 blocks.push(suite);
2892 }
2893 }
2894
2895 if (blocks.length) {
2896 this.queue.blocks = this.queue.ensured = blocks;
2897
2898 // Kill the specs that are never going to run
2899 var runningSuites = {};
2900
2901 for (i = 0, length = blocks.length; i < length; i++) {
2902 suites = blocks[i].allSuites();
2903
2904 for (var j = 0, jlen = suites.length; j < jlen; j++) {
2905 suite = suites[j];
2906
2907 runningSuites[suite.id] = true;
2908 }
2909 }
2910
2911 suites = this.suites();
2912
2913 for (i = suites.length - 1; i >= 0; i--) {
2914 suite = suites[i];
2915
2916 if (!runningSuites[suite.id]) {
2917 suites[i] = null;
2918 }
2919 }
2920 }
2921 // If suiteIds or specIds are provided, that means the user wanted to run
2922 // only the specs or suites specified. If these can't be found, most probably
2923 // that means there was a typo in a spec name, or recent changes to the spec code
2924 // changed the hash and it's no longer valid. Either way that is something that
2925 // happens only when debugging and is going to be corrected soon.
2926 // So instead of defaulting to run the whole nine yars, just bail out.
2927 else if (specs.length || suites.length) {
2928 this.queue.blocks = [];
2929 Ext.log.error('No suites or specs found!');
2930 }
2931 else {
2932 blocks = this.queue.blocks;
2933 }
2934
2935 this.env.totalSpecs = 0;
2936 for (i = 0; i < blocks.length; ++i) {
2937 this.env.totalSpecs += blocks[i].totalSpecs;
2938 }
2939 this.env.remainingSpecs = this.env.totalSpecs;
2940
2941 // We also no longer need hashes
2942 jasmine.hashes = null;
2943
2944 return this;
2945};
2946/**
2947 * Internal representation of a Jasmine specification, or test.
2948 *
2949 * @constructor
2950 * @param {jasmine.Env} env
2951 * @param {jasmine.Suite} suite
2952 * @param {String} description
2953 */
2954jasmine.Spec = function(env, suite, description) {
2955 if (!env) {
2956 throw new Error('jasmine.Env() required');
2957 }
2958 if (!suite) {
2959 throw new Error('jasmine.Suite() required');
2960 }
2961 var spec = this;
2962 spec.id = env.nextSpecId ? env.nextSpecId() : null;
2963 spec.env = env;
2964 spec.suite = suite;
2965 spec.description = description;
2966 spec.queue = new jasmine.Queue(env);
2967
2968 spec.afterCallbacks = [];
2969 spec.spies_ = [];
2970
2971 spec.results_ = new jasmine.NestedResults();
2972 spec.results_.description = description;
2973 spec.matchersClass = null;
2974};
2975
2976jasmine.Spec.prototype.getFullName = function() {
2977 return this.suite.getFullName() + ' ' + this.description + '.';
2978};
2979
2980
2981jasmine.Spec.prototype.results = function() {
2982 return this.results_;
2983};
2984
2985/**
2986 * All parameters are pretty-printed and concatenated together, then written to the spec's output.
2987 *
2988 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
2989 */
2990jasmine.Spec.prototype.log = function() {
2991 return this.results_.log(arguments);
2992};
2993
2994jasmine.Spec.prototype.runs = function (func) {
2995 var block = new jasmine.Block(this.env, func, this);
2996 this.addToQueue(block);
2997 return this;
2998};
2999
3000jasmine.Spec.prototype.addToQueue = function (block) {
3001 if (this.queue.isRunning()) {
3002 this.queue.insertNext(block);
3003 } else {
3004 this.queue.add(block);
3005 }
3006};
3007
3008/**
3009 * @param {jasmine.ExpectationResult} result
3010 */
3011jasmine.Spec.prototype.addMatcherResult = function(result) {
3012 this.results_.addResult(result);
3013};
3014
3015jasmine.Spec.prototype.expect = function(actual) {
3016 var positive = new (this.getMatchersClass_())(this.env, actual, this);
3017 positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
3018 return positive;
3019};
3020
3021/**
3022 * Waits a fixed time period before moving to the next block.
3023 *
3024 * @deprecated Use waitsFor() instead
3025 * @param {Number} timeout milliseconds to wait
3026 */
3027jasmine.Spec.prototype.waits = function(timeout) {
3028 var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
3029 this.addToQueue(waitsFunc);
3030 return this;
3031};
3032
3033/**
3034 * Waits for the latchFunction to return true before proceeding to the next block.
3035 *
3036 * @param {Function} latchFunction Function to execute
3037 * @param {String} optional_timeoutMessage Message to use if the condition is never met
3038 * @param {Number} optional_timeout Time to wait for condition to be met.
3039 * @param {Number} optional_timeout_increment Number of milliseconds to wait between invocations.
3040 */
3041jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout, optional_timeout_increment) {
3042 var latchFunction_ = null;
3043 var optional_timeoutMessage_ = null;
3044 var optional_timeout_ = null;
3045 var optional_timeout_increment_ = null;
3046 var numberFound = false;
3047
3048 for (var i = 0; i < arguments.length; i++) {
3049 var arg = arguments[i];
3050 switch (typeof arg) {
3051 case 'function':
3052 latchFunction_ = arg;
3053 break;
3054 case 'string':
3055 optional_timeoutMessage_ = arg;
3056 break;
3057 case 'number':
3058 // SECOND number is the increment
3059 if (numberFound) {
3060 optional_timeout_increment_ = arg;
3061 }
3062 // FIRST number is the timeout
3063 else {
3064 optional_timeout_ = arg;
3065 numberFound = true;
3066 }
3067 break;
3068 }
3069 }
3070
3071 var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, optional_timeout_increment_, this);
3072 this.addToQueue(waitsForFunc);
3073 return this;
3074};
3075
3076jasmine.Spec.prototype.fail = function (e) {
3077 var expectationResult = new jasmine.ExpectationResult({
3078 passed: false,
3079 message: e ? jasmine.util.formatException(e) : 'Exception',
3080 trace: { stack: e.stack }
3081 });
3082 this.results_.addResult(expectationResult);
3083};
3084
3085jasmine.Spec.prototype.getMatchersClass_ = function() {
3086 return this.matchersClass || this.env.matchersClass;
3087};
3088
3089jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
3090 var parent = this.getMatchersClass_();
3091 var newMatchersClass = function() {
3092 parent.apply(this, arguments);
3093 };
3094 jasmine.util.inherit(newMatchersClass, parent);
3095 jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
3096 this.matchersClass = newMatchersClass;
3097};
3098
3099jasmine.Spec.prototype.finishCallback = function() {
3100 this.env.reporter.reportSpecResults(this);
3101};
3102
3103jasmine.Spec.prototype.finish = function(onComplete) {
3104 this.removeAllSpies();
3105 this.finishCallback();
3106 if (onComplete) {
3107 onComplete();
3108 }
3109
3110 this.finished = true;
3111 this.env = this.afterCallbacks_ = this.spies_ = this.spy = this.matchersClass = null;
3112};
3113
3114jasmine.Spec.prototype.after = function(doAfter) {
3115 if (this.queue.isRunning()) {
3116 this.queue.add(new jasmine.Block(this.env, doAfter, this), true);
3117 } else {
3118 this.afterCallbacks.unshift(doAfter);
3119 }
3120};
3121
3122jasmine.Spec.prototype.execute = function(onComplete) {
3123 var spec = this;
3124 if (!spec.env.specFilter(spec)) {
3125 spec.results_.skipped = true;
3126 spec.finish(onComplete);
3127 return;
3128 }
3129
3130 this.env.reporter.reportSpecStarting(this);
3131
3132 spec.env.currentSpec = spec;
3133
3134 spec.addBeforesAndAftersToQueue();
3135
3136 spec.queue.start(function () {
3137 spec.finish(onComplete);
3138 });
3139};
3140
3141jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
3142 var runner = this.env.currentRunner();
3143 var i;
3144
3145 for (var suite = this.suite; suite; suite = suite.parentSuite) {
3146 for (i = 0; i < suite.before_.length; i++) {
3147 this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
3148 }
3149 }
3150 for (i = 0; i < runner.before_.length; i++) {
3151 this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
3152 }
3153 for (i = 0; i < this.afterCallbacks.length; i++) {
3154 this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true);
3155 }
3156 for (suite = this.suite; suite; suite = suite.parentSuite) {
3157 for (i = 0; i < suite.after_.length; i++) {
3158 this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true);
3159 }
3160 }
3161 for (i = 0; i < runner.after_.length; i++) {
3162 this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true);
3163 }
3164};
3165
3166jasmine.Spec.prototype.explodes = function() {
3167 throw 'explodes function should not have been called';
3168};
3169
3170jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
3171 if (obj == jasmine.undefined) {
3172 throw "spyOn could not find an object to spy upon for " + methodName + "()";
3173 }
3174
3175 if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
3176 throw methodName + '() method does not exist';
3177 }
3178
3179 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
3180 throw new Error(methodName + ' has already been spied upon');
3181 }
3182
3183 var spyObj = jasmine.createSpy(methodName);
3184
3185 this.spies_.push(spyObj);
3186 spyObj.baseObj = obj;
3187 spyObj.methodName = methodName;
3188 spyObj.originalValue = obj[methodName];
3189
3190 obj[methodName] = spyObj;
3191
3192 return spyObj;
3193};
3194
3195jasmine.Spec.prototype.removeAllSpies = function() {
3196 for (var i = 0; i < this.spies_.length; i++) {
3197 var spy = this.spies_[i];
3198 spy.baseObj[spy.methodName] = spy.originalValue;
3199 }
3200 this.spies_ = [];
3201};
3202
3203(function () {
3204 var _Spec = jasmine.Spec,
3205 proto = _Spec.prototype,
3206 allowedGlobals = {},
3207 allowedComponents = {},
3208 prop;
3209
3210 // Any properties already in the window object when we are loading jasmine are ok
3211 for (prop in window) {
3212 allowedGlobals[prop] = true;
3213 }
3214
3215 // Old Firefox needs these
3216 allowedGlobals.getInterface =
3217 allowedGlobals.loadFirebugConsole =
3218 allowedGlobals._createFirebugConsole =
3219 allowedGlobals.netscape =
3220 allowedGlobals.XPCSafeJSObjectWrapper =
3221 allowedGlobals.XPCNativeWrapper =
3222 allowedGlobals.Components =
3223 allowedGlobals._firebug =
3224 // IE10+ F12 dev tools adds these properties when opened.
3225 allowedGlobals.__IE_DEVTOOLBAR_CONSOLE_COMMAND_LINE =
3226 allowedGlobals.__BROWSERTOOLS_CONSOLE_BREAKMODE_FUNC =
3227 allowedGlobals.__BROWSERTOOLS_CONSOLE_SAFEFUNC =
3228 // in IE8 jasmine's overrides of setTimeout/setInterval make them iterable
3229 allowedGlobals.setTimeout =
3230 allowedGlobals.setInterval =
3231 allowedGlobals.clearTimeout =
3232 allowedGlobals.clearInterval =
3233 // we're going to add the addGlobal function to the window object, so specs can call it
3234 allowedGlobals.addGlobal =
3235 allowedGlobals.id = true; // In Ext JS 4 Ext.get(window) adds an id property
3236
3237 if (Ext.toolkit === 'modern') {
3238 // Modern MessageBox owns a modal mask component
3239 allowedComponents[Ext.Msg.id] = true;
3240 allowedComponents[Ext.Msg.getModal().id] = true;
3241 }
3242 else {
3243 // Ext.sparkline.Base puts this tooltip on its prototype
3244 allowedComponents['sparklines-tooltip'] = true;
3245 }
3246
3247 // Ext.MessageBox and its children are going to be present in all tests.
3248 // The reason why we don't just add allow everything that is already present
3249 // in Ext.ComponentMgr collection is that sometimes components can be created
3250 // by mistake in spec definition. We want to catch these as well.
3251 (function() {
3252 var msgbox = Ext.MessageBox,
3253 leaks = [],
3254 components = Ext.ComponentMgr.getAll();
3255
3256 for (var i = 0; i < components.length; i++) {
3257 var cmp = components[i];
3258
3259 if (cmp === msgbox || (cmp.up && cmp.up('window') === msgbox)) {
3260 allowedComponents[cmp.id] = true;
3261 }
3262 else if (!allowedComponents[cmp.id]) {
3263 leaks.push(cmp);
3264 }
3265 }
3266
3267 if (leaks.length) {
3268 Ext.log({
3269 dump: leaks,
3270 level: 'error',
3271 msg: 'COMPONENTS EXIST BEFORE TEST SUITE START'
3272 });
3273 }
3274
3275 Ext.ComponentManager.clearAll();
3276 })();
3277
3278 window.addGlobal = function(property) {
3279 var len;
3280
3281 if (property.charAt) { // string
3282 allowedGlobals[property] = true;
3283 } else { // array
3284 for (len = property.length; len--;) {
3285 allowedGlobals[property[len]] = true;
3286 }
3287 }
3288 };
3289
3290 jasmine.Spec = function () {
3291 _Spec.apply(this, arguments);
3292 this.fileName = jasmine.getCurrentScript();
3293 this.id = jasmine.hashString(this.getFullName(), this.suite.id);
3294 this.totalSpecs = 1;
3295 };
3296
3297 jasmine.Spec.prototype = proto;
3298
3299 // Override: adds the error to the result
3300 proto.fail = function (e) {
3301 var expectationResult = new jasmine.ExpectationResult({
3302 passed: false,
3303 message: e ? jasmine.util.formatException(e) : 'Exception'
3304 });
3305 // Modification start
3306 if (e instanceof Error) {
3307 expectationResult.error = e;
3308 }
3309 // Modification end
3310 this.results_.addResult(expectationResult);
3311 };
3312
3313 // Override: check for DOM and global variable leaks
3314 proto.finishCallback = function() {
3315 if (!jasmine.DISABLE_LEAK_CHECKS) {
3316 this.checkDomLeak();
3317 this.checkGlobalsLeak();
3318 this.checkComponentLeak();
3319 }
3320
3321 if (Ext.toolkit === 'classic') {
3322 this.checkLayoutSuspension();
3323 this.checkFocusSuspension();
3324 }
3325
3326 // TODO: this causes too many failures so is disabled for now.
3327 // clean up orphan elements and re-enable this at some point.
3328 // this.collectGarbage();
3329
3330 Ext.event.publisher.Gesture.instance.reset();
3331
3332 this.env.reporter.reportSpecResults(this);
3333
3334 // Once the results have been reported, we don't need to keep them anymore;
3335 // except when we're running under Cmd. Unlike local reporter, Cmd collects
3336 // results in batches per suite so we need to keep the results until the
3337 // collection is done. Cmd will then run the suite cleanup.
3338 if (!window.Cmd) {
3339 this.cleanupResults();
3340 }
3341 };
3342
3343 proto.cleanupResults = function() {
3344 var results = this.results();
3345
3346 if (results) {
3347 results.cleanup();
3348 }
3349 };
3350
3351 proto.checkDomLeak = function() {
3352 var body = document.body,
3353 children = body && body.childNodes || [],
3354 len = children.length,
3355 badNodes = [],
3356 badIds = [],
3357 i = 0,
3358 child, ids;
3359
3360 for (; i < len; i++) {
3361 child = children[i];
3362
3363 if (child.nodeType === 3 || !child.getAttribute('data-sticky')) {
3364 badIds.push(child.tagName + '#' + child.id);
3365 badNodes.push(child);
3366 }
3367 }
3368
3369 for (i = 0, len = badNodes.length; i < len; i++) {
3370 document.body.removeChild(badNodes[i]);
3371 }
3372
3373 if (badNodes.length) {
3374 ids = badIds.join(', ');
3375
3376 Ext.log({
3377 dump: badNodes,
3378 level: 'error',
3379 msg: 'CLEAN UP YOUR DOM LEAKS IN SPEC: ' + this.getFullName()
3380 });
3381
3382 this.fail('document.body contains childNodes after spec execution --> ' + this.getFullName());
3383 }
3384 };
3385
3386 proto.checkComponentLeak = function(spec) {
3387 var leaks = [],
3388 components, i, len, cmp;
3389
3390 components = Ext.ComponentMgr.getAll();
3391
3392 for (i = 0, len = components.length; i < len; i++) {
3393 cmp = components[i];
3394
3395 // Allow QuickTips by default, they're mostly harmless
3396 if (!allowedComponents[cmp.id] &&
3397 !(cmp.isQuickTip || (cmp.up && cmp.up('[isQuickTip]')))) {
3398 leaks.push(cmp);
3399 }
3400 }
3401
3402 // We don't destroy the leaked components so that they could be examined,
3403 // just clearing them from ComponentManager cache is enough
3404 Ext.ComponentMgr.clearAll();
3405
3406 if (leaks.length) {
3407 Ext.log({
3408 dump: leaks,
3409 level: 'error',
3410 msg: 'CLEAN UP YOUR COMPONENT LEAKS IN SPEC: ' + this.getFullName()
3411 });
3412
3413 this.fail('Ext.ComponentMgr reports undestroyed components after spec execution');
3414 }
3415 };
3416
3417 proto.checkGlobalsLeak = function(spec) {
3418 var property, value;
3419
3420 for (property in window) {
3421 try {
3422 // IE throws error when trying to access window.localStorage
3423 value = window[property];
3424 } catch(e) {
3425 continue;
3426 }
3427 if (value !== undefined && !allowedGlobals[property] &&
3428 (!value || // make sure we don't try to do a property lookup on a null value
3429 // old browsers (IE6 and opera 11) add element IDs as enumerable properties
3430 // of the window object, so make sure the global var is not a HTMLElement
3431 value.nodeType !== 1 &&
3432 // make sure it isn't a reference to a window object. This happens in
3433 // some browsers (e.g. IE6) when the document contains iframes. The
3434 // frames' window objects are referenced by id in the parent window object.
3435 !(value.location && value.document))) {
3436 this.fail('Bad global variable: ' + property + ' = ' + value);
3437 // add the bad global to allowed globals so that it only fails this one spec
3438 allowedGlobals[property] = true;
3439 }
3440 }
3441 };
3442
3443 proto.checkLayoutSuspension = function(spec) {
3444 var count = Ext.Component.layoutSuspendCount;
3445 if (count !== 0) {
3446 this.fail('Spec completed with layouts suspended: count=' + count);
3447 Ext.Component.layoutSuspendCount = 0;
3448 }
3449 };
3450
3451 proto.checkFocusSuspension = function(spec) {
3452 // If the ExtJS version supports focus suspension...
3453 if (Ext.suspendFocus) {
3454 var count = Ext.event.publisher.Focus.instance.suspendCount;
3455 if (count) {
3456 this.fail('Spec completed with focus suspended: count=' + count);
3457 Ext.event.publisher.Focus.instance.suspendCount = 0;
3458 }
3459 }
3460 };
3461
3462 proto.collectGarbage = function() {
3463 var ids = Ext.dom.GarbageCollector.collect();
3464
3465 if (ids.length) {
3466 this.fail("Orphan Ext.dom.Element(s) detected: '" + ids.join("', '") + "'");
3467 }
3468 };
3469
3470 proto.execute = function(onComplete) {
3471 var spec = this;
3472 if (!spec.env.specFilter(spec)) {
3473 spec.results_.skipped = true;
3474 onComplete();
3475 return;
3476 }
3477
3478 this.env.reporter.reportSpecStarting(this);
3479
3480 if (spec.isDisabled()) {
3481 spec.results_.skipped = true;
3482 spec.finish(onComplete);
3483 return;
3484 }
3485
3486 spec.env.currentSpec = spec;
3487
3488 spec.addBeforesAndAftersToQueue();
3489
3490 if (spec.debugBlocks && jasmine.getOptions().debug === true) {
3491 var blockIdx = jasmine.getOptions().block;
3492 if (typeof blockIdx !== 'undefined') {
3493 blockIdx = parseInt(blockIdx);
3494 var blocks = this.queue.blocks,
3495 length = blocks.length,
3496 i = 0,
3497 block;
3498
3499 for (; i < length; i++) {
3500 block = blocks[i];
3501 if (i === blockIdx) {
3502 block.func = jasmine.generateDebuggableBlock(block.func);
3503 }
3504 }
3505 }
3506 jasmine.showDebugPrompt(function() {
3507 spec.queue.start(function () {
3508 spec.finish(onComplete);
3509 });
3510 });
3511 } else {
3512 spec.queue.start(function () {
3513 spec.finish(onComplete);
3514 });
3515 }
3516 };
3517
3518 proto.enabled = true;
3519
3520 proto.isEnabled = function() {
3521 return this.enabled;
3522 };
3523
3524 proto.isDisabled = function() {
3525 return !this.enabled;
3526 };
3527
3528 proto.disable = function() {
3529 this.enabled = false;
3530
3531 // Release bound contexts and closures
3532 this.queue.finish();
3533
3534 return this;
3535 };
3536
3537 proto.enable = function() {
3538 this.enabled = true;
3539
3540 return this;
3541 };
3542
3543 proto.getRootSuite = function() {
3544 var suite = this.suite;
3545
3546 while (suite.parentSuite) {
3547 suite = suite.parentSuite;
3548 }
3549
3550 return suite;
3551 };
3552})();
3553
3554/**
3555 * Works just like waits() and waitsFor(), except waits for the next animationFrame
3556 */
3557function waitsForAnimation() {
3558 var done = false;
3559 runs(function() {
3560 Ext.Function.requestAnimationFrame(function() {
3561 setTimeout(function() {
3562 done = true;
3563 }, 1);
3564 });
3565 });
3566 waitsFor(function() {
3567 return done;
3568 });
3569}
3570
3571/**
3572 * Waits for the Spy to have been called before proceeding to the next block.
3573 *
3574 * @param {Function} spy to wait for
3575 * @param {String} [timeoutMessage] Optional timeout message
3576 * @param {Number} [timeout] Optional timeout in ms
3577 */
3578function waitsForSpy(spy, timeoutMessage, timeout) {
3579 var currentSpec = jasmine.getEnv().currentSpec;
3580
3581 timeoutMessage = timeoutMessage || spy.identity + ' to fire';
3582 timeout = timeout != null ? timeout : 1000;
3583
3584 currentSpec.waitsFor.call(currentSpec, function() { return !!spy.callCount }, timeoutMessage, timeout);
3585};
3586
3587var waitForSpy = waitsForSpy;
3588
3589/**
3590 * Internal representation of a Jasmine suite.
3591 *
3592 * @constructor
3593 * @param {jasmine.Env} env
3594 * @param {String} description
3595 * @param {Function} specDefinitions
3596 * @param {jasmine.Suite} parentSuite
3597 */
3598jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
3599 var self = this;
3600 self.id = env.nextSuiteId ? env.nextSuiteId() : null;
3601 self.description = description;
3602 self.queue = new jasmine.Queue(env);
3603 self.parentSuite = parentSuite;
3604 self.env = env;
3605 self.before_ = [];
3606 self.after_ = [];
3607 self.children_ = [];
3608 self.suites_ = [];
3609 self.specs_ = [];
3610};
3611
3612jasmine.Suite.prototype.getFullName = function() {
3613 var fullName = this.description;
3614 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
3615 fullName = parentSuite.description + ' ' + fullName;
3616 }
3617 return fullName;
3618};
3619
3620jasmine.Suite.prototype.finish = function(onComplete) {
3621 this.env.reporter.reportSuiteResults(this);
3622 this.finished = true;
3623 if (typeof(onComplete) == 'function') {
3624 onComplete();
3625 }
3626
3627 // MUST NOT null the children_ property because that is needed to
3628 // traverse the suite's child nodes upon expand and collapse.
3629 this.env = this.before_ = this.after_ = null;
3630 this.suites_ = this.specs_ = null;
3631};
3632
3633jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
3634 beforeEachFunction.typeName = 'beforeEach';
3635 this.before_.unshift(beforeEachFunction);
3636};
3637
3638jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
3639 afterEachFunction.typeName = 'afterEach';
3640 this.after_.unshift(afterEachFunction);
3641};
3642
3643jasmine.Suite.prototype.results = function() {
3644 return this.queue.results();
3645};
3646
3647jasmine.Suite.prototype.add = function(suiteOrSpec) {
3648 this.children_.push(suiteOrSpec);
3649 if (suiteOrSpec instanceof jasmine.Suite) {
3650 this.suites_.push(suiteOrSpec);
3651 this.env.currentRunner().addSuite(suiteOrSpec);
3652 } else {
3653 this.specs_.push(suiteOrSpec);
3654 }
3655 this.queue.add(suiteOrSpec);
3656};
3657
3658jasmine.Suite.prototype.specs = function() {
3659 return this.specs_;
3660};
3661
3662jasmine.Suite.prototype.suites = function() {
3663 return this.suites_;
3664};
3665
3666jasmine.Suite.prototype.children = function() {
3667 return this.children_;
3668};
3669
3670jasmine.Suite.prototype.execute = function(onComplete) {
3671 var self = this;
3672 this.queue.start(function () {
3673 self.finish(onComplete);
3674 });
3675};
3676(function () {
3677 var _Suite = jasmine.Suite,
3678 proto = _Suite.prototype;
3679
3680 jasmine.Suite = function () {
3681 _Suite.apply(this, arguments);
3682
3683 var parentSuite = this.parentSuite;
3684 this.totalSpecs = 0;
3685 this.fileName = jasmine.getCurrentScript();
3686 this.id = jasmine.hashString(this.getFullName(), parentSuite ? parentSuite.id : 0);
3687 };
3688
3689 jasmine.Suite.prototype = proto;
3690
3691 proto.execute = function(onComplete) {
3692 var self = this;
3693 self.env.reporter.reportSuiteStarting(self); // override
3694
3695 if (self.isDisabled()) {
3696 self.results = self.forceSkippedResults;
3697 self.disableChildren();
3698 }
3699
3700 this.queue.start(function () {
3701 self.finish(onComplete);
3702 });
3703 };
3704
3705 proto.enabled = true;
3706
3707 proto.isEnabled = function() {
3708 return this.enabled;
3709 };
3710
3711 proto.isDisabled = function() {
3712 return !this.enabled;
3713 };
3714
3715 proto.adjustCounts = function (amount) {
3716 for (var suite = this; suite; suite = suite.parentSuite) {
3717 suite.totalSpecs += amount;
3718 }
3719 };
3720
3721 proto.disable = function() {
3722 this.enabled = false;
3723 return this;
3724 };
3725
3726 proto.enable = function() {
3727 this.enabled = true;
3728 return this;
3729 };
3730
3731 proto.forceSkippedResults = function() {
3732 var results = this.queue.results();
3733 results.skipped = true;
3734
3735 return results;
3736 };
3737
3738 proto.disableChildren = function() {
3739 var children = this.children(),
3740 length = children.length,
3741 i = 0;
3742
3743 for (; i < length; i++) {
3744 children[i].disable();
3745 }
3746
3747 return this;
3748 };
3749
3750 proto.filter = function (suiteIds, specIds) {
3751 if (!suiteIds[this.id]) {
3752 var specs = this.specs(),
3753 suites = this.suites(),
3754 spec, i, suite, length;
3755
3756 length = specs.length;
3757
3758 for (i = 0; i < length; i++) {
3759 spec = specs[i];
3760 if (!specIds[spec.id]) {
3761 jasmine.array.remove(this.queue.blocks, spec);
3762 this.adjustCounts(-spec.totalSpecs);
3763 }
3764 }
3765
3766 length = suites.length;
3767
3768 for (i = 0; i < length; i++) {
3769 suite = suites[i];
3770 suite.filter(suiteIds, specIds);
3771 if (suite.empty) {
3772 jasmine.array.remove(this.queue.blocks, suite);
3773 this.adjustCounts(-suite.totalSpecs);
3774 }
3775 }
3776
3777 if (this.queue.blocks.length === 0) {
3778 this.empty = true;
3779 }
3780 }
3781
3782 return this;
3783 };
3784
3785 proto.getRootSuite = function() {
3786 var suite = this;
3787
3788 while (suite.parentSuite) {
3789 suite = suite.parentSuite;
3790 }
3791
3792 return suite;
3793 };
3794
3795 proto.add = function(suiteOrSpec) {
3796 this.children_.push(suiteOrSpec);
3797 if (suiteOrSpec instanceof jasmine.Suite) {
3798 this.suites_.push(suiteOrSpec);
3799 this.env.currentRunner().addSuite(suiteOrSpec);
3800 } else {
3801 this.specs_.push(suiteOrSpec);
3802 }
3803 this.queue.add(suiteOrSpec);
3804
3805 for (var p = this; p; p = p.parentSuite) {
3806 p.totalSpecs += suiteOrSpec.totalSpecs;
3807 }
3808 };
3809
3810 proto.allSuites = function() {
3811 var all = [],
3812 suites, subSuites, i, len;
3813
3814 suites = this.suites();
3815
3816 for (i = 0, len = suites.length; i < len; i++) {
3817 all.push(suites[i]);
3818
3819 subSuites = suites[i].allSuites();
3820
3821 if (subSuites.length) {
3822 all.push(subSuites);
3823 }
3824 }
3825
3826 return all.length ? Ext.Array.flatten(all) : all;
3827 };
3828
3829 proto.cleanupResults = function() {
3830 var results = this.results();
3831
3832 if (results) {
3833 results.cleanup();
3834 }
3835 };
3836})();
3837jasmine.WaitsBlock = function(env, timeout, spec) {
3838 this.timeout = timeout;
3839 jasmine.Block.call(this, env, null, spec);
3840};
3841
3842jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
3843
3844jasmine.WaitsBlock.prototype.execute = function (onComplete) {
3845 if (jasmine.VERBOSE) {
3846 this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
3847 }
3848 this.env.setTimeout(function () {
3849 onComplete();
3850 }, this.timeout);
3851};
3852/**
3853 * A block which waits for some condition to become true, with timeout.
3854 *
3855 * @constructor
3856 * @extends jasmine.Block
3857 * @param {jasmine.Env} env The Jasmine environment.
3858 * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
3859 * @param {Function} latchFunction A function which returns true when the desired condition has been met.
3860 * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
3861 * @param {NUmber} timeout_increment Time in milliseconds to wait between invocations.
3862 * @param {jasmine.Spec} spec The Jasmine spec.
3863 */
3864jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, timeout_increment, spec) {
3865 this.timeout = timeout || env.defaultTimeoutInterval;
3866 this.latchFunction = latchFunction;
3867 this.message = message;
3868 this.totalTimeSpentWaitingForLatch = 0;
3869 this.timeout_increment = timeout_increment || jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
3870 jasmine.Block.call(this, env, null, spec);
3871};
3872jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
3873
3874jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
3875
3876jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
3877 if (jasmine.VERBOSE) {
3878 this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
3879 }
3880 var latchFunctionResult;
3881 try {
3882 latchFunctionResult = this.latchFunction.call(this.spec, this.timeout, this.totalTimeSpentWaitingForLatch);
3883 } catch (e) {
3884 this.spec.fail(e);
3885 onComplete();
3886 return;
3887 }
3888
3889 if (latchFunctionResult) {
3890 onComplete();
3891 } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
3892 var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
3893 this.spec.fail({
3894 name: 'timeout',
3895 message: message
3896 });
3897
3898 this.abort = true;
3899 onComplete();
3900 } else {
3901 this.totalTimeSpentWaitingForLatch += this.timeout_increment;
3902 var self = this;
3903 this.env.setTimeout(function() {
3904 self.execute(onComplete);
3905 }, this.timeout_increment);
3906 }
3907};
3908// Mock setTimeout, clearTimeout
3909// Contributed by Pivotal Computer Systems, www.pivotalsf.com
3910
3911jasmine.FakeTimer = function() {
3912 this.reset();
3913
3914 var self = this;
3915 self.setTimeout = function(funcToCall, millis) {
3916 self.timeoutsMade++;
3917 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
3918 return self.timeoutsMade;
3919 };
3920
3921 self.setInterval = function(funcToCall, millis) {
3922 self.timeoutsMade++;
3923 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
3924 return self.timeoutsMade;
3925 };
3926
3927 self.clearTimeout = function(timeoutKey) {
3928 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
3929 };
3930
3931 self.clearInterval = function(timeoutKey) {
3932 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
3933 };
3934
3935};
3936
3937jasmine.FakeTimer.prototype.reset = function() {
3938 this.timeoutsMade = 0;
3939 this.scheduledFunctions = {};
3940 this.nowMillis = 0;
3941};
3942
3943jasmine.FakeTimer.prototype.tick = function(millis) {
3944 var oldMillis = this.nowMillis;
3945 var newMillis = oldMillis + millis;
3946 this.runFunctionsWithinRange(oldMillis, newMillis);
3947 this.nowMillis = newMillis;
3948};
3949
3950jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
3951 var scheduledFunc;
3952 var funcsToRun = [];
3953 for (var timeoutKey in this.scheduledFunctions) {
3954 scheduledFunc = this.scheduledFunctions[timeoutKey];
3955 if (scheduledFunc != jasmine.undefined &&
3956 scheduledFunc.runAtMillis >= oldMillis &&
3957 scheduledFunc.runAtMillis <= nowMillis) {
3958 funcsToRun.push(scheduledFunc);
3959 this.scheduledFunctions[timeoutKey] = jasmine.undefined;
3960 }
3961 }
3962
3963 if (funcsToRun.length > 0) {
3964 funcsToRun.sort(function(a, b) {
3965 return a.runAtMillis - b.runAtMillis;
3966 });
3967 for (var i = 0; i < funcsToRun.length; ++i) {
3968 try {
3969 var funcToRun = funcsToRun[i];
3970 this.nowMillis = funcToRun.runAtMillis;
3971 funcToRun.funcToCall();
3972 if (funcToRun.recurring) {
3973 this.scheduleFunction(funcToRun.timeoutKey,
3974 funcToRun.funcToCall,
3975 funcToRun.millis,
3976 true);
3977 }
3978 } catch(e) {
3979 }
3980 }
3981 this.runFunctionsWithinRange(oldMillis, nowMillis);
3982 }
3983};
3984
3985jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
3986 this.scheduledFunctions[timeoutKey] = {
3987 runAtMillis: this.nowMillis + millis,
3988 funcToCall: funcToCall,
3989 recurring: recurring,
3990 timeoutKey: timeoutKey,
3991 millis: millis
3992 };
3993};
3994
3995/**
3996 * @namespace
3997 */
3998jasmine.Clock = {
3999 defaultFakeTimer: new jasmine.FakeTimer(),
4000
4001 reset: function() {
4002 jasmine.Clock.assertInstalled();
4003 jasmine.Clock.defaultFakeTimer.reset();
4004 },
4005
4006 tick: function(millis) {
4007 jasmine.Clock.assertInstalled();
4008 jasmine.Clock.defaultFakeTimer.tick(millis);
4009 },
4010
4011 runFunctionsWithinRange: function(oldMillis, nowMillis) {
4012 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
4013 },
4014
4015 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
4016 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
4017 },
4018
4019 useMock: function() {
4020 if (!jasmine.Clock.isInstalled()) {
4021 var spec = jasmine.getEnv().currentSpec;
4022 spec.after(jasmine.Clock.uninstallMock);
4023
4024 jasmine.Clock.installMock();
4025 }
4026 },
4027
4028 installMock: function() {
4029 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
4030 },
4031
4032 uninstallMock: function() {
4033 jasmine.Clock.assertInstalled();
4034 jasmine.Clock.installed = jasmine.Clock.real;
4035 },
4036
4037 real: {
4038 setTimeout: jasmine.getGlobal().setTimeout,
4039 clearTimeout: jasmine.getGlobal().clearTimeout,
4040 setInterval: jasmine.getGlobal().setInterval,
4041 clearInterval: jasmine.getGlobal().clearInterval
4042 },
4043
4044 assertInstalled: function() {
4045 if (!jasmine.Clock.isInstalled()) {
4046 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
4047 }
4048 },
4049
4050 isInstalled: function() {
4051 return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
4052 },
4053
4054 installed: null
4055};
4056jasmine.Clock.installed = jasmine.Clock.real;
4057
4058//else for IE support
4059jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
4060 if (jasmine.Clock.installed.setTimeout.apply) {
4061 return jasmine.Clock.installed.setTimeout.apply(this, arguments);
4062 } else {
4063 return jasmine.Clock.installed.setTimeout(funcToCall, millis);
4064 }
4065};
4066
4067jasmine.getGlobal().setInterval = function(funcToCall, millis) {
4068 if (jasmine.Clock.installed.setInterval.apply) {
4069 return jasmine.Clock.installed.setInterval.apply(this, arguments);
4070 } else {
4071 return jasmine.Clock.installed.setInterval(funcToCall, millis);
4072 }
4073};
4074
4075jasmine.getGlobal().clearTimeout = function(timeoutKey) {
4076 if (jasmine.Clock.installed.clearTimeout.apply) {
4077 return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
4078 } else {
4079 return jasmine.Clock.installed.clearTimeout(timeoutKey);
4080 }
4081};
4082
4083jasmine.getGlobal().clearInterval = function(timeoutKey) {
4084 if (jasmine.Clock.installed.clearTimeout.apply) {
4085 return jasmine.Clock.installed.clearInterval.apply(this, arguments);
4086 } else {
4087 return jasmine.Clock.installed.clearInterval(timeoutKey);
4088 }
4089};
4090
4091jasmine.mouseToPointerMap = {
4092 mousedown: 'pointerdown',
4093 mousemove: 'pointermove',
4094 mouseup: 'pointerup',
4095 mouseover: 'pointerover',
4096 mouseout: 'pointerout',
4097 mouseenter: 'pointerenter',
4098 mouseleave: 'pointerleave'
4099};
4100
4101jasmine.pointerEventsMap = Ext.supports.MSPointerEvents && !Ext.supports.PointerEvents ? {
4102 // translation map for IE10
4103 pointerdown: 'MSPointerDown',
4104 pointermove: 'MSPointerMove',
4105 pointerup: 'MSPointerUp',
4106 pointerover: 'MSPointerOver',
4107 pointerout: 'MSPointerOut',
4108 // IE10 does not have pointer events for enter/leave
4109 pointerenter: 'mouseenter',
4110 pointerleave: 'mouseleave'
4111} : {};
4112
4113/**
4114 * Utility function to fire a fake mouse event to a given target element
4115 */
4116jasmine.fireMouseEvent = function (target, type, x, y, button, shiftKey, ctrlKey, altKey) {
4117 var e, doc, docEl, body, ret, pointerEventType;
4118
4119 target = Ext.getDom(target && target.isComponent ? target.el : target);
4120 if (!target) {
4121 throw 'Cannot fire mouse event on null element';
4122 }
4123 doc = target.ownerDocument || document;
4124 x = x || 0;
4125 y = y || 0;
4126
4127 if (Ext.isIE9m && doc.createEventObject){ // IE event model
4128 e = doc.createEventObject();
4129 docEl = doc.documentElement;
4130 body = doc.body;
4131 x = x + (docEl && docEl.clientLeft || 0) + (body && body.clientLeft || 0);
4132 y = y + (docEl && docEl.clientTop || 0) + (body && body.clientLeft || 0);
4133 Ext.apply(e, {
4134 bubbles: true,
4135 cancelable: true,
4136 screenX: x,
4137 screenY: y,
4138 clientX: x,
4139 clientY: y,
4140 button: button || 1,
4141 shiftKey: !!shiftKey,
4142 ctrlKey: !!ctrlKey,
4143 altKey: !!altKey
4144 });
4145 if (type === 'click') {
4146 target.fireEvent('onmousedown', e);
4147 target.fireEvent('onmouseup', e);
4148 } else if (type === 'dblclick') {
4149 jasmine.fireMouseEvent(target, 'click', x, y, button, shiftKey, ctrlKey, altKey);
4150 target.fireEvent('onmousedown', e);
4151 target.fireEvent('onmouseup', e);
4152 }
4153 ret = target.fireEvent('on' + type, e);
4154 } else {
4155 if (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents) {
4156 // In IE10 and higher the framework translates mouse event listeners to pointer
4157 // event listeners by default. This means that if we fire only mouse events, our
4158 // pointer event listeners will not be fired. To fix this, we have to emulate
4159 // what the browser does when the mouse is clicked or screen is touched - fire
4160 // a pointer event followed by a compatibility mouse event.
4161 // see http://www.w3.org/TR/pointerevents/#dfn-compatibility-mouse-events
4162 if (type === 'click') {
4163 // In IE10+ the framework translates click to tap, which means we must
4164 // fire the events from which tap is sythesized (pointerdown/pointerup)
4165 // if we want our listeners to run.
4166 jasmine.firePointerEvent(target, 'pointerdown', 1, x, y, button, shiftKey, ctrlKey, altKey);
4167 jasmine.doFireMouseEvent(target, 'mousedown', x, y, button, shiftKey, ctrlKey, altKey);
4168 jasmine.firePointerEvent(target, 'pointerup', 1, x, y, button, shiftKey, ctrlKey, altKey);
4169 jasmine.doFireMouseEvent(target, 'mouseup', x, y, button, shiftKey, ctrlKey, altKey);
4170 } else if (type === 'dblclick') {
4171 // click (which triggers its own (pointerdown/mousdown/pointerup/mouseup
4172 // sequence) followed by a second pointerdown/mousedown/pointerup/mouseup
4173 // sequence always precedes dblclick
4174 jasmine.fireMouseEvent(target, 'click', x, y, button, shiftKey, ctrlKey, altKey);
4175 jasmine.firePointerEvent(target, 'pointerdown', 1, x, y, button, shiftKey, ctrlKey, altKey);
4176 jasmine.doFireMouseEvent(target, 'mousedown', x, y, button, shiftKey, ctrlKey, altKey);
4177 jasmine.firePointerEvent(target, 'pointerup', 1, x, y, button, shiftKey, ctrlKey, altKey);
4178 jasmine.doFireMouseEvent(target, 'mouseup', x, y, button, shiftKey, ctrlKey, altKey);
4179 } else {
4180 // plain old mouse event (mousedown, mousemove, etc.) - fire the corresponding
4181 // pointer event before dispatching the mouse event
4182 pointerEventType = jasmine.mouseToPointerMap[type];
4183 if (pointerEventType) {
4184 jasmine.firePointerEvent(target, pointerEventType, 1, x, y, button);
4185 }
4186 }
4187 } else if (type === 'click') {
4188 // simulate a mousedown/mouseup sequence before firing a click event
4189 jasmine.fireMouseEvent(target, 'mousedown', x, y, button, shiftKey, ctrlKey, altKey);
4190 jasmine.fireMouseEvent(target, 'mouseup', x, y, button, shiftKey, ctrlKey, altKey);
4191 } else if (type === 'dblclick') {
4192 // click (which includes its own mousedown/mouseup sequence) followed by a second
4193 // mousedown/mouseup always precedes dblclick
4194 jasmine.fireMouseEvent(target, 'click', x, y, button, shiftKey, ctrlKey, altKey);
4195 jasmine.fireMouseEvent(target, 'mousedown', x, y, button, shiftKey, ctrlKey, altKey);
4196 jasmine.fireMouseEvent(target, 'mouseup', x, y, button, shiftKey, ctrlKey, altKey);
4197 }
4198
4199 ret = jasmine.doFireMouseEvent(target, type, x, y, button, shiftKey, ctrlKey, altKey);
4200 }
4201
4202 return (ret === false) ? ret : e;
4203};
4204
4205jasmine.doFireMouseEvent = function(target, type, x, y, button, shiftKey, ctrlKey, altKey) {
4206 var doc = target.ownerDocument || document,
4207 e = doc.createEvent("MouseEvents");
4208
4209 e.initMouseEvent(type, true, true, doc.defaultView || doc.parentWindow, 1, x, y, x, y, !!ctrlKey, !!altKey, !!shiftKey, false, button || 0, null);
4210 return target.dispatchEvent(e);
4211};
4212
4213/**
4214 * Fires a pointer event. Since PointerEvents cannot yet be directly constructed,
4215 * we fake it by constructing a mouse event and setting its pointer id. This method
4216 * should typically be used when (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents).
4217 * @param {String/Ext.Element/HTMLElement} target
4218 * @param {String} type The name of the event to fire
4219 * @param {Number} pointerId A unique id for the pointer, for more on pointerId see
4220 * http://www.w3.org/TR/pointerevents/
4221 * @param {Number} x The x coordinate
4222 * @param {Number} y The y coordinate
4223 * @param {Number} button
4224 * @return {Boolean} true if the event was successfully dispatched
4225 */
4226jasmine.firePointerEvent = function(target, type, pointerId, x, y, button, shiftKey, ctrlKey, altKey) {
4227 var doc = document,
4228 e = doc.createEvent("MouseEvents"),
4229 target = Ext.getDom(target),
4230 dispatched;
4231
4232 if (!target) {
4233 throw 'Cannot fire pointer event on null element';
4234 }
4235
4236 type = jasmine.pointerEventsMap[type] || type;
4237
4238 e.initMouseEvent(
4239 type, // type
4240 true, // canBubble
4241 true, // cancelable
4242 doc.defaultView || doc.parentWindow, // view
4243 1, // detail
4244 x, // screenX
4245 y, // screenY
4246 x, // clientX
4247 y, // clientY
4248 !!ctrlKey, // ctrlKey
4249 !!altKey, // altKey
4250 !!shiftKey, // shiftKey
4251 false, // metaKey
4252 button || 0, // button
4253 null // relatedTarget
4254 );
4255 e.pointerId = pointerId || 1;
4256 e.pointerType = 'mouse';
4257
4258 dispatched = target.dispatchEvent(e);
4259
4260 return (dispatched === false) ? dispatched : e;
4261};
4262
4263jasmine.createTouchList = function(touchList, target) {
4264 var doc = document,
4265 i = 0,
4266 ln = touchList.length,
4267 touches = [],
4268 touchCfg;
4269
4270 for (; i < ln; i++) {
4271 touchCfg = touchList[i];
4272 touches.push(doc.createTouch(
4273 doc.defaultView || doc.parentWindow,
4274 touchCfg.target || target,
4275 // use 1 as the default ID, so that tests that are only concerned with a single
4276 // touch event don't need to worry about providing an ID
4277 touchCfg.identifier || 1,
4278 touchCfg.pageX,
4279 touchCfg.pageY,
4280 touchCfg.screenX || touchCfg.pageX, // use pageX/Y as the default for screenXY
4281 touchCfg.screenY || touchCfg.pageY
4282 ));
4283 }
4284
4285 return doc.createTouchList.apply(doc, touches);
4286};
4287
4288/**
4289 * Utility for emulating a touch event. This method should typically only be used when
4290 * Ext.supports.TouchEvents. Recommended reading for understanding how touch events work:
4291 * http://www.w3.org/TR/touch-events/
4292 * @param {String/Ext.Element/HTMLElement} target
4293 * @param {String} type The name of the event to fire
4294 * @param {Object[]} touches An array of config objects for constructing the event object's
4295 * "touches". The config objects conform to the following interface:
4296 * http://www.w3.org/TR/touch-events/#idl-def-Touch The only required properties
4297 * are pageX and pageY. this method provides defaults for the others.
4298 * @param {Object[]} changedTouches An array of config objects for constructing the event
4299 * object's "changedTouches" (defaults to the same value as the `touches` param)
4300 * @param {Object[]} targetTouches An array of config objects for constructing the event
4301 * object's "targetTouches" (defaults to the same value as the `touches` param)
4302 * @param {Number} scale
4303 * @param {Number} rotation
4304 * @return {Boolean} true if the event was successfully dispatched
4305 */
4306jasmine.fireTouchEvent = function(target, type, touches, changedTouches, targetTouches) {
4307 var doc = document,
4308 // Couldn't figure out how to set touches, changedTouches targetTouches on a "real"
4309 // TouchEvent, initTouchEvent seems to ignore the parameters documented here:
4310 // https://developer.apple.com/library/safari/documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.htmlTouchLists
4311 // Apparently directly assigning to e.touches after creating a TouchEvent doesn't
4312 // work either so the best we can do is just make a CustomEvent and fake it.
4313 e = new CustomEvent(type, {
4314 bubbles: true,
4315 cancelable: true
4316 }),
4317 target = Ext.getDom(target),
4318 dispatched;
4319
4320 if (!target) {
4321 throw 'Cannot fire touch event on null element';
4322 }
4323
4324 Ext.apply(e, {
4325 target: target,
4326 touches: jasmine.createTouchList(touches, target),
4327 changedTouches: jasmine.createTouchList(changedTouches ? changedTouches : touches, target),
4328 targetTouches: jasmine.createTouchList(targetTouches ? targetTouches : touches, target)
4329 });
4330
4331 dispatched = target.dispatchEvent(e);
4332
4333 return (dispatched === false) ? dispatched : e;
4334};
4335
4336/**
4337 * Utility function to fire a fake key event to a given target element
4338 */
4339jasmine.fireKeyEvent = function(target, type, key, shiftKey, ctrlKey, altKey) {
4340 var e,
4341 doc;
4342 target = Ext.getDom(target);
4343 if (!target) {
4344 throw 'Cannot fire key event on null element';
4345 }
4346 doc = target.ownerDocument || document;
4347 if (Ext.isIE9m && doc.createEventObject) { //IE event model
4348 e = doc.createEventObject();
4349 Ext.apply(e, {
4350 bubbles: true,
4351 cancelable: true,
4352 keyCode: key,
4353 shiftKey: !!shiftKey,
4354 ctrlKey: !!ctrlKey,
4355 altKey: !!altKey
4356 });
4357 return target.fireEvent('on' + type, e);
4358 } else {
4359 e = doc.createEvent("Events");
4360 e.initEvent(type, true, true);
4361 Ext.apply(e, {
4362 keyCode: key,
4363 shiftKey: !!shiftKey,
4364 ctrlKey: !!ctrlKey,
4365 altKey: !!altKey
4366 });
4367 return target.dispatchEvent(e);
4368 }
4369};
4370
4371// This implementation is pretty naïve but since it's not easy to simulate
4372// real Tab key presses (if at all possible), it doesn't make sense to go
4373// any further than this.
4374jasmine.simulateTabKey = jasmine.syncPressTabKey = function(from, forward) {
4375 function getNextTabTarget(currentlyFocused, forward) {
4376 var selector = 'a[href],button,iframe,input,select,textarea,[tabindex],[contenteditable="true"]',
4377 body = Ext.getBody(),
4378 currentDom, focusables, node, next,
4379 len, lastIdx, currIdx, i, to, step;
4380
4381 currentDom = Ext.getDom(currentlyFocused);
4382 focusables = body.dom.querySelectorAll(selector);
4383 len = focusables.length;
4384 lastIdx = focusables.length - 1;
4385 currIdx = Ext.Array.indexOf(focusables, currentDom);
4386
4387 // If the currently focused element is not present in the list,
4388 // it must be the body itself. Just focus the first or last
4389 // tabbable element.
4390 if (currIdx < 0) {
4391 if (forward) {
4392 i = 0;
4393 to = len - 1;
4394 step = 1;
4395 }
4396 else {
4397 i = len - 1;
4398 to = 0;
4399 step = -1;
4400 }
4401 }
4402 else {
4403 if (forward) {
4404 i = currIdx + 1;
4405 to = len - 1;
4406 step = 1;
4407 }
4408 else {
4409 i = currIdx - 1;
4410 to = 0;
4411 step = -1;
4412 }
4413 }
4414
4415 // We're only interested in the elements that an user can *tab into*,
4416 // not all programmatically focusable elements. So we have to filter
4417 // these out.
4418 for (;; i += step) {
4419 if ((step > 0 && i > to) || (step < 0 && i < to)) {
4420 break;
4421 }
4422
4423 node = focusables[i];
4424
4425 if (Ext.fly(node).isTabbable()) {
4426 next = node;
4427 break;
4428 }
4429 }
4430
4431 return Ext.get(next || body);
4432 }
4433
4434 from = from.isComponent ? from.getFocusEl() : from;
4435
4436 jasmine.fireKeyEvent(from, 'keydown', 9, forward);
4437
4438 // Compute the next target *after* firing keydown;
4439 // a handler somewhere could have changed tabbability!
4440 // Not only that but the focused element could have changed
4441 // as well so we have to account for it.
4442 var to = getNextTabTarget(document.activeElement || from, forward);
4443
4444 if (to) {
4445 to.focus();
4446 }
4447
4448 jasmine.fireKeyEvent(to || from, 'keyup', 9, forward);
4449
4450 return to;
4451};
4452
4453jasmine.simulateArrowKey = jasmine.syncPressArrowKey =
4454jasmine.simulateKey = jasmine.syncPressKey = function(from, key, options) {
4455 var keyCode = Ext.event.Event[key.toUpperCase()];
4456
4457 if (keyCode === undefined) {
4458 throw 'Cannot fire undefined key event!';
4459 }
4460
4461 from = from.isComponent ? from.getFocusEl() : from;
4462
4463 var target = Ext.getDom(from);
4464
4465 if (!target) {
4466 throw 'Cannot fire arrow key event on null element';
4467 }
4468
4469 var shiftKey, ctrlKey, altKey;
4470
4471 if (options) {
4472 shiftKey = options.shift || options.shiftKey;
4473 ctrlKey = options.ctrl || options.ctrlKey;
4474 altKey = options.alt || options.altKey;
4475 }
4476
4477 jasmine.fireKeyEvent(target, 'keydown', keyCode, shiftKey, ctrlKey, altKey);
4478
4479 // IE8 blows up with "unspecified error" :(
4480 if (!Ext.isIE8) {
4481 jasmine.fireKeyEvent(target, 'keyup', keyCode, shiftKey, ctrlKey, altKey);
4482 }
4483};
4484
4485// In IE, focus events are asynchronous so we often have to wait
4486// after attempting to focus something. Otherwise tests will fail.
4487jasmine.waitForFocus = jasmine.waitsForFocus = function(cmp, desc, timeout) {
4488 var isComponent = cmp.isComponent,
4489 dom = isComponent ? cmp.getFocusEl().dom
4490 : cmp.isElement ? cmp.dom
4491 : cmp,
4492 id = isComponent ? (cmp.itemId || cmp.id) : dom.id;
4493
4494 if (!desc) {
4495 desc = id + ' to focus';
4496 }
4497
4498 timeout = timeout || 1000;
4499
4500 waitsFor(
4501 function() {
4502 return document.activeElement === dom;
4503 },
4504 desc,
4505 timeout
4506 );
4507};
4508
4509jasmine.waitForBlur = jasmine.waitsForBlur = function(cmp, desc, timeout) {
4510 var dom = cmp.isComponent ? cmp.getFocusEl().dom
4511 : cmp.isElement ? cmp.dom
4512 : cmp;
4513
4514 if (!desc) {
4515 desc = dom.id + ' to blur';
4516 }
4517
4518 timeout = timeout || 1000;
4519
4520 waitsFor(
4521 function() {
4522 return document.activeElement !== dom;
4523 },
4524 desc,
4525 timeout
4526 );
4527};
4528
4529// In IE (all of 'em), focus/blur events are asynchronous. To us it means
4530// not only that we have to wait for the actual element to focus but
4531// also for its container-injected focus handler to fire; and since
4532// container focus handler may focus yet another element we have to yield
4533// for *that* focus handler to fire, too. The third `waits` is to
4534// accommodate for any repercussions caused by secondary focus handler,
4535// and of course as a good luck charm.
4536// Note that the timeout value is not important here because effectively
4537// we just want to yield enough cycles to unwind all the async event handlers
4538// before the test checks done in the specs, so we default to 1 ms.
4539jasmine.waitAWhile = jasmine.waitsAWhile = function(timeout) {
4540 timeout = timeout != null ? timeout : 1;
4541
4542 waits(timeout);
4543 waits(timeout);
4544 waits(timeout);
4545};
4546
4547jasmine.focusAndWait = function(cmp, waitFor) {
4548 // Apparently IE has yet another odd problem with focusing some elements;
4549 // if dom.focus() is called before the element is fully initialized, focusing
4550 // will fail and focus will jump to the document body. This happens with
4551 // text inputs at the very least, maybe with some others as well.
4552 // In IE9-10 we work around this issue by giving it a bit of time to finish
4553 // whatever initialization it was doing; in IE8 some harsher measures are
4554 // required, see Ext.dom.Element override.
4555 if (Ext.isIE10m) {
4556 jasmine.waitAWhile();
4557 }
4558
4559 runs(function() {
4560 cmp.focus();
4561 });
4562
4563 jasmine.waitForFocus(waitFor || cmp);
4564
4565 jasmine.waitAWhile();
4566};
4567
4568jasmine.blurAndWait = function(cmp, waitFor) {
4569 runs(function() {
4570 // Programmatic blur fails on IEs, so just focus the body element instead.
4571 if (Ext.isIE) {
4572 Ext.getBody().focus();
4573 }
4574 else {
4575 cmp.blur();
4576 }
4577 });
4578
4579 jasmine.waitForBlur(waitFor || cmp);
4580
4581 jasmine.waitAWhile();
4582};
4583
4584jasmine.pressTabKey = jasmine.asyncPressTabKey = function(from, forward) {
4585 jasmine.focusAndWait(from, from);
4586
4587 runs(function() {
4588 jasmine.simulateTabKey(from, forward);
4589 });
4590
4591 jasmine.waitAWhile();
4592};
4593
4594jasmine.pressArrowKey = jasmine.asyncPressArrowKey =
4595jasmine.pressKey = jasmine.asyncPressKey = function(from, key, options) {
4596 jasmine.focusAndWait(from);
4597
4598 runs(function() {
4599 jasmine.simulateArrowKey(from, key, options);
4600 });
4601
4602 jasmine.waitAWhile();
4603};
4604
4605// Can't add this one and below as simple matchers,
4606// because there's async waiting involved
4607jasmine.expectFocused = jasmine.expectsFocused = function(want, noWait) {
4608 if (!noWait) {
4609 jasmine.waitForFocus(want);
4610 }
4611
4612 runs(function() {
4613 var have = want.isComponent ? Ext.ComponentManager.getActiveComponent()
4614 : want.isElement ? Ext.fly(document.activeElement)
4615 : document.activeElement
4616 ;
4617
4618 expect(have).toBe(want);
4619 });
4620};
4621
4622jasmine.expectTabIndex = jasmine.expectsTabIndex = function(wantIndex, el) {
4623 runs(function() {
4624 if (el && el.isComponent) {
4625 el = el.getFocusEl();
4626 }
4627
4628 var haveIndex = el.dom.getAttribute('tabIndex');
4629
4630 expect(haveIndex - 0).toBe(wantIndex);
4631 });
4632};
4633
4634jasmine.expectAriaAttr = function(cmp, attr, value) {
4635 var target = cmp.isComponent ? cmp.ariaEl : Ext.get(cmp);
4636
4637 expect(target.dom.getAttribute(attr)).toBe(value);
4638};
4639
4640jasmine.expectNoAriaAttr = function(cmp, attr) {
4641 var target = cmp.isComponent ? cmp.ariaEl : Ext.get(cmp);
4642
4643 expect(target.dom.hasAttribute(attr)).toBe(false);
4644};
4645
4646/*
4647This version is commented out because there is a bug in WebKit that prevents
4648key events to be fired with both options and a valid keycode. When the bug gets fixed
4649this method can be reintroduced. See https://bugs.webkit.org/show_bug.cgi?id=16735
4650jasmine.fireKeyEvent = function(target, type, key, options) {
4651 var e, ret, prop;
4652
4653 options = options || {};
4654 target = Ext.getDom(target);
4655 if (document.createEventObject) { //IE event model
4656 e = document.createEventObject();
4657 Ext.apply(e, {
4658 bubbles: true,
4659 cancelable: true,
4660 keyCode: key
4661 });
4662 if (options) {
4663 for (prop in options) {
4664 if (options.hasOwnProperty(prop)) {
4665 e[prop] = options[prop];
4666 }
4667 }
4668 }
4669 return target.fireEvent('on' + type, e);
4670 }
4671 else {
4672 e = document.createEvent('KeyboardEvent');
4673 if (typeof e.initKeyboardEvent != 'undefined') {
4674 e.initKeyboardEvent(type, true, true, window,
4675 false,
4676 false,
4677 false,
4678 false, 97, 97);
4679 } else {
4680 e.initKeyEvent(type, true, true, window,
4681 options.ctrlKey || false,
4682 options.altKey || false,
4683 options.shiftKey || false,
4684 options.metaKey || false, key, key);
4685 }
4686 return target.dispatchEvent(e);
4687 }
4688};
4689*/
4690var fakeScope = {
4691 id: "fakeScope",
4692 fakeScope: true
4693};
4694/**
4695 * Class to act as a bridge between the MockAjax class and Ext.data.Request
4696 */
4697var MockAjaxManager = {
4698
4699 getXhrInstance: null,
4700
4701 /**
4702 * Pushes methods onto the Request prototype to make it easier to deal with
4703 */
4704 addMethods: function(){
4705 var Connection = Ext.data.Connection,
4706 connectionProto = Connection.prototype,
4707 requestProto = Ext.data.request.Ajax.prototype;
4708
4709 Connection.requestId = 0;
4710 MockAjaxManager.getXhrInstance = requestProto.getXhrInstance;
4711
4712 /**
4713 * Template method to create the AJAX request
4714 */
4715 requestProto.getXhrInstance = function() {
4716 return new MockAjax();
4717 };
4718
4719 /**
4720 * Method to simulate a request completing
4721 * @param {Object} response The response
4722 * @param {String} id (optional) The id of the completed request
4723 */
4724 connectionProto.mockComplete = function(response, id) {
4725 var request = this.mockGetRequestXHR(id);
4726
4727 if (request) {
4728 request.xhr.complete(response);
4729 }
4730 };
4731
4732 /**
4733 * Get a particular request
4734 * @param {String} id (optional) The id of the request
4735 */
4736 connectionProto.mockGetRequestXHR = function(id) {
4737 var request;
4738
4739 if (id) {
4740 request = this.requests[id];
4741 } else {
4742 // get the first one
4743 request = this.mockGetAllRequests()[0];
4744 }
4745 return request ? request : null;
4746 };
4747
4748 /**
4749 * Gets all the requests from the Connection
4750 */
4751 connectionProto.mockGetAllRequests = function(){
4752 var requests = this.requests,
4753 id,
4754 request,
4755 out = [];
4756
4757 for (id in requests) {
4758 if (requests.hasOwnProperty(id)) {
4759 out.push(requests[id]);
4760 }
4761 }
4762 return out;
4763 };
4764
4765 this.originalExtAjax = Ext.Ajax;
4766 Ext.Ajax = new Connection({ autoAbort : false });
4767 },
4768
4769 /**
4770 * Restore any changes made by addMethods
4771 */
4772 removeMethods: function() {
4773 var proto = Ext.data.Connection.prototype;
4774 delete proto.mockComplete;
4775 delete proto.mockGetRequestXHR;
4776 Ext.Ajax = this.originalExtAjax;
4777
4778 Ext.data.request.Ajax.prototype.getXhrInstance = MockAjaxManager.getXhrInstance;
4779 MockAjaxManager.getXhrInstance = null;
4780 }
4781};
4782
4783/**
4784 * Simple Mock class to represent an XMLHttpRequest
4785 */
4786var MockAjax = function(){
4787 /**
4788 * Contains all request headers
4789 */
4790 this.headers = {};
4791
4792 /**
4793 * Contains any options specified during sending
4794 */
4795 this.ajaxOptions = {};
4796
4797 this.readyState = 0;
4798
4799 this.status = null;
4800
4801 this.responseText = this.responseXML = null;
4802};
4803
4804/**
4805 * Contains a default response for any synchronous request.
4806 */
4807MockAjax.prototype.syncDefaults = {
4808 responseText: 'data',
4809 status: 200,
4810 statusText: '',
4811 responseXML: null,
4812 responseHeaders: {"Content-type": "application/json" }
4813};
4814
4815MockAjax.prototype.readyChange = function() {
4816 if (this.onreadystatechange) {
4817 this.onreadystatechange();
4818 }
4819};
4820
4821/**
4822 * Simulate the XHR open method
4823 * @param {Object} method
4824 * @param {Object} url
4825 * @param {Object} async
4826 * @param {Object} username
4827 * @param {Object} password
4828 */
4829MockAjax.prototype.open = function(method, url, async, username, password){
4830 var options = this.ajaxOptions;
4831 options.method = method;
4832 options.url = url;
4833 options.async = async;
4834 options.username = username;
4835 options.password = password;
4836 this.readyState = 1;
4837 this.readyChange();
4838};
4839
4840/**
4841 * Simulate the XHR send method
4842 * @param {Object} data
4843 */
4844MockAjax.prototype.send = function(data){
4845 this.ajaxOptions.data = data;
4846 this.readyState = 2;
4847 // if it's a synchronous request, let's just assume it's already finished
4848 if (!this.ajaxOptions.async) {
4849 this.complete(this.syncDefaults);
4850 } else {
4851 this.readyChange();
4852 }
4853};
4854
4855/**
4856 * Simulate the XHR abort method
4857 */
4858MockAjax.prototype.abort = function(){
4859 this.readyState = 0;
4860 this.readyChange();
4861};
4862
4863/**
4864 * Simulate the XHR setRequestHeader method
4865 * @param {Object} header
4866 * @param {Object} value
4867 */
4868MockAjax.prototype.setRequestHeader = function(header, value){
4869 this.headers[header] = value;
4870};
4871
4872/**
4873 * Simulate the XHR getAllResponseHeaders method
4874 */
4875MockAjax.prototype.getAllResponseHeaders = function(){
4876 var headers = this.responseHeaders,
4877 lines = [],
4878 header;
4879
4880 for (header in headers) {
4881 if (headers.hasOwnProperty(header)) {
4882 lines.push(header + ': ' + headers[header]);
4883 }
4884 }
4885
4886 return lines.join('\r\n');
4887};
4888
4889/**
4890 * Simulate the XHR getResponseHeader method
4891 * @param {Object} name
4892 */
4893MockAjax.prototype.getResponseHeader = function(name){
4894 return this.responseHeaders[header];
4895};
4896
4897/**
4898 * Simulate the XHR onreadystatechange method
4899 */
4900MockAjax.prototype.onreadystatechange = function(){
4901};
4902
4903/**
4904 * Method for triggering a response completion
4905 */
4906MockAjax.prototype.complete = function(response){
4907 this.responseText = response.responseText || '';
4908 this.status = response.status;
4909 this.statusText = response.statusText;
4910 this.responseXML = response.responseXML || this.xmlDOM(response.responseText);
4911 this.responseHeaders = response.responseHeaders || {"Content-type": response.contentType || "application/json" };
4912 this.readyState = 4;
4913 this.readyChange();
4914};
4915
4916/**
4917 * Converts string to XML DOM
4918 */
4919MockAjax.prototype.xmlDOM = function(xml) {
4920 // IE DOMParser support
4921 if (!window.DOMParser && window.ActiveXObject) {
4922 doc = new ActiveXObject('Microsoft.XMLDOM');
4923 doc.async = 'false';
4924 DOMParser = function() {};
4925 DOMParser.prototype.parseFromString = function(xmlString) {
4926 var doc = new ActiveXObject('Microsoft.XMLDOM');
4927 doc.async = 'false';
4928 doc.loadXML(xmlString);
4929 return doc;
4930 };
4931 }
4932
4933 if (xml && xml.substr(0, 1) === '<') {
4934 try {
4935 return (new DOMParser()).parseFromString(xml, "text/xml");
4936 }
4937 catch (e) {}
4938 }
4939
4940 return null;
4941};