r93781 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r93780‎ | r93781 | r93782 >
Date:20:33, 2 August 2011
Author:neilk
Status:ok
Tags:
Comment:
moving URI library to main resources
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizardHooks.php (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.Uri.js (deleted) (history)
  • /trunk/extensions/UploadWizard/test/jasmine/lib/jasmine-1.0.1 (deleted) (history)
  • /trunk/extensions/UploadWizard/test/jasmine/spec/mw.Uri.spec.js (deleted) (history)
  • /trunk/phase3/resources/Resources.php (modified) (history)
  • /trunk/phase3/resources/mediawiki/mediawiki.uri.js (added) (history)
  • /trunk/phase3/tests/jasmine/SpecRunner.html (added) (history)
  • /trunk/phase3/tests/jasmine/lib/jasmine-1.0.1 (added) (history)
  • /trunk/phase3/tests/jasmine/spec/mediawiki.uri.spec.js (added) (history)

Diff [purge]

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

Follow-up revisions

RevisionCommit summaryAuthorDate
r94230Rename mw.uri to mw.Uri + minor fixes:...krinkle04:09, 11 August 2011

Status & tagging log