├── .gitignore ├── bower.json ├── component.json ├── README.md ├── composer.json └── pavlov.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | composer.lock 3 | vendor 4 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pavlov", 3 | "dependencies": { 4 | "qunit": "~1.11.0" 5 | }, 6 | "main": [ 7 | "pavlov.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pavlov", 3 | "repo": "components/pavlov", 4 | "homepage": "https://github.com/mmonteleone/pavlov", 5 | "dependencies": { 6 | "components/qunit": "~1.11.0" 7 | }, 8 | "scripts": [ 9 | "pavlov.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pavlov 2 | ====== 3 | 4 | Shim repository for [Pavlov](http://github.com/mmonteleone/pavlov), the QUnit 5 | Behavioral API. 6 | 7 | 8 | Package Managers 9 | ---------------- 10 | 11 | * [Composer](http://packagist.org/packages/components/pavlov): `components/pavlov` 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components/pavlov", 3 | "description": "Behavioral API over QUnit.", 4 | "type": "component", 5 | "keywords": [ 6 | "qunit", 7 | "javascript", 8 | "bdd" 9 | ], 10 | "homepage": "https://github.com/mmonteleone/pavlov", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Michael Montelone", 15 | "homepage": "http://michaelmonteleone.net" 16 | } 17 | ], 18 | "require": { 19 | "components/qunit": "*" 20 | }, 21 | "extra": { 22 | "component": { 23 | "scripts": [ 24 | "pavlov.js" 25 | ], 26 | "shim": { 27 | "exports": "window.pavlov", 28 | "deps": ["qunit"] 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pavlov.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pavlov - Test framework-independent behavioral API 3 | * 4 | * version 0.3.0 5 | * 6 | * http://github.com/mmonteleone/pavlov 7 | * 8 | * Copyright (c) 2009-2011 Michael Monteleone 9 | * Licensed under terms of the MIT License (README.markdown) 10 | */ 11 | (function (global) { 12 | 13 | // =========== 14 | // = Helpers = 15 | // =========== 16 | 17 | var util = { 18 | /** 19 | * Iterates over an object or array 20 | * @param {Object|Array} object object or array to iterate 21 | * @param {Function} callback callback for each iterated item 22 | */ 23 | each: function (object, callback) { 24 | if (typeof object === 'undefined' || typeof callback === 'undefined' 25 | || object === null || callback === null) { 26 | throw "both 'target' and 'callback' arguments are required"; 27 | } 28 | var name, 29 | i = 0, 30 | length = object.length, 31 | value; 32 | 33 | if (length === undefined) { 34 | for (name in object) { 35 | if (object.hasOwnProperty(name)) { 36 | if (callback.call( object[name], name, object[name]) === false) { 37 | break; 38 | } 39 | } 40 | } 41 | } else { 42 | for (value = object[0]; 43 | i < length && callback.call(value, i, value) !== false; 44 | value = object[++i]) { 45 | } 46 | } 47 | 48 | return object; 49 | }, 50 | /** 51 | * converts an array-like object to an array 52 | * @param {Object} array array-like object 53 | * @returns array 54 | */ 55 | makeArray: function (array) { 56 | return Array.prototype.slice.call(array); 57 | }, 58 | /** 59 | * returns whether or not an object is an array 60 | * @param {Object} obj object to test 61 | * @returns whether or not object is array 62 | */ 63 | isArray: function (obj) { 64 | return Object.prototype.toString.call(obj) === "[object Array]"; 65 | }, 66 | /** 67 | * merges properties form one object to another 68 | * @param {Object} dest object to receive merged properties 69 | * @param {Object} src object containing properies to merge 70 | */ 71 | extend: function (dest, src) { 72 | if (typeof dest === 'undefined' || typeof src === 'undefined' || 73 | dest === null || src === null) { 74 | throw "both 'source' and 'target' arguments are required"; 75 | } 76 | var prop; 77 | for (prop in src) { 78 | if (src.hasOwnProperty(prop)) { 79 | dest[prop] = src[prop]; 80 | } 81 | } 82 | }, 83 | /** 84 | * Naive display serializer for objects which wraps the objects' 85 | * own toString() value with type-specific delimiters. 86 | * [] for array 87 | * "" for string 88 | * Does not currently go nearly detailed enough for JSON use, 89 | * just enough to show small values within test results 90 | * @param {Object} obj object to serialize 91 | * @returns naive display-serialized string representation of the object 92 | */ 93 | serialize: function (obj) { 94 | if (typeof obj === 'undefined') { 95 | return ""; 96 | } else if (Object.prototype.toString.call(obj) === "[object Array]") { 97 | return '[' + obj.toString() + ']'; 98 | } else if (Object.prototype.toString.call(obj) === "[object Function]") { 99 | return "function()"; 100 | } else if (typeof obj === "string") { 101 | return '"' + obj + '"'; 102 | } else { 103 | return obj; 104 | } 105 | }, 106 | /** 107 | * transforms a camel or pascal case string 108 | * to all lower-case space-separated phrase 109 | * @param {string} value pascal or camel-cased string 110 | * @returns all-lower-case space-separated phrase 111 | */ 112 | phraseCase: function (value) { 113 | return value.replace(/([A-Z])/g, ' $1').toLowerCase(); 114 | } 115 | }; 116 | 117 | 118 | // ==================== 119 | // = Example Building = 120 | // ==================== 121 | 122 | var examples = [], 123 | currentExample, 124 | /** 125 | * Rolls up list of current and ancestors values for given prop name 126 | * @param {String} prop Name of property to roll up 127 | * @returns array of values corresponding to prop name 128 | */ 129 | rollup = function (example, prop) { 130 | var items = []; 131 | while (example !== null) { 132 | items.push(example[prop]); 133 | example = example.parent; 134 | } 135 | return items; 136 | }; 137 | 138 | /** 139 | * Example Class 140 | * Represents an instance of an example (a describe) 141 | * contains references to parent and nested examples 142 | * exposes methods for returning combined lists of before, after, and names 143 | * @constructor 144 | * @param {example} parent example to append self as child to (optional) 145 | */ 146 | function Example(parent) { 147 | if (parent) { 148 | // if there's a parent, append self as nested example 149 | this.parent = parent; 150 | this.parent.children.push(this); 151 | } else { 152 | // otherwise, add this as a new root example 153 | examples.push(this); 154 | } 155 | 156 | this.children = []; 157 | this.specs = []; 158 | } 159 | util.extend(Example.prototype, { 160 | name: '', // name of this description 161 | parent: null, // parent example 162 | children: [], // nested examples 163 | specs: [], // array of it() tests/specs 164 | before: function () {}, // called before all contained specs 165 | after: function () {}, // called after all contained specs 166 | /** 167 | * rolls up this and ancestor's before functions 168 | * @returns array of functions 169 | */ 170 | befores: function () { 171 | return rollup(this, 'before').reverse(); 172 | }, 173 | /** 174 | * Rolls up this and ancestor's after functions 175 | * @returns array of functions 176 | */ 177 | afters: function () { 178 | return rollup(this, 'after'); 179 | }, 180 | /** 181 | * Rolls up this and ancestor's description names, joined 182 | * @returns string of joined description names 183 | */ 184 | names: function () { 185 | return rollup(this, 'name').reverse().join(', '); 186 | } 187 | }); 188 | 189 | 190 | 191 | // ============== 192 | // = Assertions = 193 | // ============== 194 | 195 | /** 196 | * AssertionHandler 197 | * represents instance of an assertion regarding a particular 198 | * actual value, and provides an api around asserting that value 199 | * against any of the bundled assertion handlers and custom ones. 200 | * @constructor 201 | * @param {Object} value A test-produced value to assert against 202 | */ 203 | function AssertionHandler(value) { 204 | this.value = value; 205 | } 206 | 207 | /** 208 | * Appends assertion methods to the AssertionHandler prototype 209 | * For each provided assertion implementation, adds an identically named 210 | * assertion function to assertionHandler prototype which can run implementation 211 | * @param {Object} asserts Object containing assertion implementations 212 | */ 213 | var addAssertions = function (asserts) { 214 | util.each(asserts, function (name, fn) { 215 | AssertionHandler.prototype[name] = function () { 216 | // implement this handler against backend 217 | // by pre-pending AssertionHandler's current value to args 218 | var args = util.makeArray(arguments); 219 | args.unshift(this.value); 220 | 221 | // if no explicit message was given with the assertion, 222 | // then let's build our own friendly one 223 | if (fn.length === 2) { 224 | args[1] = args[1] || 'asserting ' + util.serialize(args[0]) + ' ' + util.phraseCase(name); 225 | } else if (fn.length === 3) { 226 | var expected = util.serialize(args[1]); 227 | args[2] = args[2] || 'asserting ' + util.serialize(args[0]) + ' ' + util.phraseCase(name) + (expected ? ' ' + expected : expected); 228 | } 229 | 230 | fn.apply(this, args); 231 | }; 232 | }); 233 | }; 234 | 235 | /** 236 | * Add default assertions 237 | */ 238 | addAssertions({ 239 | equals: function (actual, expected, message) { 240 | adapter.assert(actual == expected, message); 241 | }, 242 | isEqualTo: function (actual, expected, message) { 243 | adapter.assert(actual == expected, message); 244 | }, 245 | isNotEqualTo: function (actual, expected, message) { 246 | adapter.assert(actual != expected, message); 247 | }, 248 | isStrictlyEqualTo: function (actual, expected, message) { 249 | adapter.assert(actual === expected, message); 250 | }, 251 | isNotStrictlyEqualTo: function (actual, expected, message) { 252 | adapter.assert(actual !== expected, message); 253 | }, 254 | isTrue: function (actual, message) { 255 | adapter.assert(actual, message); 256 | }, 257 | isFalse: function (actual, message) { 258 | adapter.assert(!actual, message); 259 | }, 260 | isNull: function (actual, message) { 261 | adapter.assert(actual === null, message); 262 | }, 263 | isNotNull: function (actual, message) { 264 | adapter.assert(actual !== null, message); 265 | }, 266 | isDefined: function (actual, message) { 267 | adapter.assert(typeof actual !== 'undefined', message); 268 | }, 269 | isUndefined: function (actual, message) { 270 | adapter.assert(typeof actual === 'undefined', message); 271 | }, 272 | pass: function (actual, message) { 273 | adapter.assert(true, message); 274 | }, 275 | fail: function (actual, message) { 276 | adapter.assert(false, message); 277 | }, 278 | isFunction: function(actual, message) { 279 | return adapter.assert(typeof actual === "function", message); 280 | }, 281 | isNotFunction: function (actual, message) { 282 | return adapter.assert(typeof actual !== "function", message); 283 | }, 284 | throwsException: function (actual, expectedErrorDescription, message) { 285 | // can optionally accept expected error message 286 | try { 287 | actual(); 288 | adapter.assert(false, message); 289 | } catch (e) { 290 | // so, this bit of weirdness is basically a way to allow for the fact 291 | // that the test may have specified a particular type of error to catch, or not. 292 | // and if not, e would always === e. 293 | adapter.assert(e === (expectedErrorDescription || e), message); 294 | } 295 | } 296 | }); 297 | 298 | 299 | // ===================== 300 | // = pavlov Public API = 301 | // ===================== 302 | 303 | 304 | /** 305 | * Object containing methods to be made available as public API 306 | */ 307 | var api = { 308 | /** 309 | * Initiates a new Example context 310 | * @param {String} description Name of what's being "described" 311 | * @param {Function} fn Function containing description (before, after, specs, nested examples) 312 | */ 313 | describe: function (description, fn) { 314 | if (arguments.length < 2) { 315 | throw "both 'description' and 'fn' arguments are required"; 316 | } 317 | 318 | // capture reference to current example before construction 319 | var originalExample = currentExample; 320 | try { 321 | // create new current example for construction 322 | currentExample = new Example(currentExample); 323 | currentExample.name = description; 324 | fn(); 325 | } finally { 326 | // restore original reference after construction 327 | currentExample = originalExample; 328 | } 329 | }, 330 | 331 | /** 332 | * Sets a function to occur before all contained specs and nested examples' specs 333 | * @param {Function} fn Function to be executed 334 | */ 335 | before: function (fn) { 336 | if (arguments.length === 0) { 337 | throw "'fn' argument is required"; 338 | } 339 | currentExample.before = fn; 340 | }, 341 | 342 | /** 343 | * Sets a function to occur after all contained tests and nested examples' tests 344 | * @param {Function} fn Function to be executed 345 | */ 346 | after: function (fn) { 347 | if (arguments.length === 0) { 348 | throw "'fn' argument is required"; 349 | } 350 | currentExample.after = fn; 351 | }, 352 | 353 | /** 354 | * Creates a spec (test) to occur within an example 355 | * When not passed fn, creates a spec-stubbing fn which asserts fail "Not Implemented" 356 | * @param {String} specification Description of what "it" "should do" 357 | * @param {Function} fn Function containing a test to assert that it does indeed do it (optional) 358 | */ 359 | it: function (specification, fn) { 360 | if (arguments.length === 0) { 361 | throw "'specification' argument is required"; 362 | } 363 | if (fn) { 364 | if (fn.async) { 365 | specification += " asynchronously"; 366 | } 367 | currentExample.specs.push([specification, fn]); 368 | } else { 369 | // if not passed an implementation, create an implementation that simply asserts fail 370 | api.it(specification, function () {api.assert.fail('Not Implemented');}); 371 | } 372 | }, 373 | 374 | /** 375 | * wraps a spec (test) implementation with an initial call to pause() the test runner 376 | * The spec must call resume() when ready 377 | * @param {Function} fn Function containing a test to assert that it does indeed do it (optional) 378 | */ 379 | async: function (fn) { 380 | var implementation = function () { 381 | adapter.pause(); 382 | fn.apply(this, arguments); 383 | }; 384 | implementation.async = true; 385 | return implementation; 386 | }, 387 | 388 | /** 389 | * Generates a row spec for each argument passed, applying 390 | * each argument to a new call against the spec 391 | * @returns an object with an it() function for defining 392 | * function to be called for each of given's arguments 393 | * @param {Array} arguments either list of values or list of arrays of values 394 | */ 395 | given: function () { 396 | if (arguments.length === 0) { 397 | throw "at least one argument is required"; 398 | } 399 | var args = util.makeArray(arguments); 400 | if (arguments.length === 1 && util.isArray(arguments[0])) { 401 | args = args[0]; 402 | } 403 | 404 | return { 405 | /** 406 | * Defines a row spec (test) which is applied against each 407 | * of the given's arguments. 408 | */ 409 | it: function (specification, fn) { 410 | util.each(args, function () { 411 | var arg = this; 412 | api.it("given " + arg + ", " + specification, function () { 413 | fn.apply(this, util.isArray(arg) ? arg : [arg]); 414 | }); 415 | }); 416 | } 417 | }; 418 | }, 419 | 420 | /** 421 | * Assert a value against any of the bundled or custom assertions 422 | * @param {Object} value A value to be asserted 423 | * @returns an AssertionHandler instance to fluently perform an assertion with 424 | */ 425 | assert: function (value) { 426 | return new AssertionHandler(value); 427 | }, 428 | 429 | /** 430 | * specifies test runner to synchronously wait 431 | * @param {Number} ms Milliseconds to wait 432 | * @param {Function} fn Function to execute after ms has 433 | * passed before resuming 434 | */ 435 | wait: function (ms, fn) { 436 | if (arguments.length < 2) { 437 | throw "both 'ms' and 'fn' arguments are required"; 438 | } 439 | adapter.pause(); 440 | global.setTimeout(function () { 441 | fn(); 442 | adapter.resume(); 443 | }, ms); 444 | }, 445 | 446 | /** 447 | * specifies test framework to pause test runner 448 | */ 449 | pause: function () { 450 | adapter.pause(); 451 | }, 452 | 453 | /** 454 | * specifies test framework to resume test runner 455 | */ 456 | resume: function () { 457 | adapter.resume(); 458 | } 459 | }; 460 | 461 | // extend api's assert function for easier access to 462 | // parameter-less assert.pass() and assert.fail() calls 463 | util.each(['pass', 'fail'], function (i, method) { 464 | api.assert[method] = function (message) { 465 | api.assert()[method](message); 466 | }; 467 | }); 468 | 469 | /** 470 | * Extends a function's scope 471 | * applies the extra scope to the function returns un-run new version of fn 472 | * inspired by Yehuda Katz's metaprogramming Screw.Unit 473 | * different in that new function can still accept all parameters original function could 474 | * @param {Function} fn Target function for extending 475 | * @param {Object} thisArg Object for the function's "this" to refer 476 | * @param {Object} extraScope object whose members will be added to fn's scope 477 | * @returns Modified version of original function with extra scope. Can still 478 | * accept parameters of original function 479 | */ 480 | var extendScope = function (fn, thisArg, extraScope) { 481 | 482 | // get a string of the fn's parameters 483 | var params = fn.toString().match(/\(([^\)]*)\)/)[1], 484 | // get a string of fn's body 485 | source = fn.toString().match(/^[^\{]*\{((.*\s*)*)\}/m)[1]; 486 | 487 | // create a new function with same parameters and 488 | // body wrapped in a with(extraScope) { } 489 | fn = new Function ( 490 | "extraScope" + (params ? ", " + params : ""), 491 | "with(extraScope) {" + source + "}"); 492 | 493 | // returns a fn wrapper which takes passed args, 494 | // pre-pends extraScope arg, and applies to modified fn 495 | return function () { 496 | var args = [extraScope]; 497 | util.each(arguments,function () { 498 | args.push(this); 499 | }); 500 | fn.apply(thisArg, args); 501 | }; 502 | }; 503 | 504 | /** 505 | * Top-level Specify method. Declares a new pavlov context 506 | * @param {String} name Name of what's being specified 507 | * @param {Function} fn Function containing exmaples and specs 508 | */ 509 | var specify = function (name, fn) { 510 | if (arguments.length < 2) { 511 | throw "both 'name' and 'fn' arguments are required"; 512 | } 513 | examples = []; 514 | currentExample = null; 515 | 516 | // set the test suite title 517 | name += " Specifications"; 518 | if (typeof document !== 'undefined') { 519 | document.title = name + ' - Pavlov - ' + adapter.name; 520 | } 521 | 522 | // run the adapter initiation 523 | adapter.initiate(name); 524 | 525 | if (specify.globalApi) { 526 | // if set to extend global api, 527 | // extend global api and run example builder 528 | util.extend(global, api); 529 | fn(); 530 | } else { 531 | // otherwise, extend example builder's scope with api 532 | // and run example builder 533 | extendScope(fn, this, api)(); 534 | } 535 | 536 | // compile examples against the adapter and then run them 537 | adapter.compile(name, examples)(); 538 | }; 539 | 540 | // ==================================== 541 | // = Test Framework Adapter Interface = 542 | // ==================================== 543 | 544 | // abstracts functionality of underlying testing framework 545 | var adapter = { 546 | /** 547 | * adapter-specific initialization code 548 | * which is called once before any tests are run 549 | * @param {String} suiteName name of the pavlov suite name 550 | */ 551 | initiate: function (suiteName) { }, 552 | /** 553 | * adapter-specific assertion method 554 | * @param {bool} expr Boolean expression to assert against 555 | * @param {String} message message to pass along with assertion 556 | */ 557 | assert: function (expr, message) { 558 | throw "'assert' must be implemented by a test framework adapter"; 559 | }, 560 | /** 561 | * adapter-specific compilation method. Translates a nested set of 562 | * pre-constructed Pavlov example objects into a callable function which, when run 563 | * will execute the tests within the backend test framework 564 | * @param {String} suiteName name of overall test suite 565 | * @param {Array} examples Array of example object instances, possibly nesteds 566 | */ 567 | compile: function (suiteName, examples) { 568 | throw "'compile' must be implemented by a test framework adapter"; 569 | }, 570 | /** 571 | * adapter-specific pause method. When an adapter implements, 572 | * allows for its test runner to pause its execution 573 | */ 574 | pause: function () { 575 | throw "'pause' not implemented by current test framework adapter"; 576 | }, 577 | /** 578 | * adapter-specific resume method. When an adapter implements, 579 | * allows for its test runner to resume after a pause 580 | */ 581 | resume: function () { 582 | throw "'resume' not implemented by current test framework adapter"; 583 | } 584 | }; 585 | 586 | 587 | // ===================== 588 | // = Expose Public API = 589 | // ===================== 590 | 591 | // add global settings onto pavlov 592 | global.pavlov = { 593 | version: '0.3.0', 594 | specify: specify, 595 | adapter: adapter, 596 | adapt: function (frameworkName, testFrameworkAdapter) { 597 | if ( typeof frameworkName === "undefined" || 598 | typeof testFrameworkAdapter === "undefined" || 599 | frameworkName === null || 600 | testFrameworkAdapter === null) { 601 | throw "both 'frameworkName' and 'testFrameworkAdapter' arguments are required"; 602 | } 603 | adapter.name = frameworkName; 604 | util.extend(adapter, testFrameworkAdapter); 605 | }, 606 | util: { 607 | each: util.each, 608 | extend: util.extend 609 | }, 610 | api: api, 611 | globalApi: false, // when true, adds api to global scope 612 | extendAssertions: addAssertions // function for adding custom assertions 613 | }; 614 | }(window)); 615 | 616 | 617 | // ========================= 618 | // = Default QUnit Adapter = 619 | // ========================= 620 | 621 | (function () { 622 | if (typeof QUnit === 'undefined') { return; } 623 | 624 | pavlov.adapt("QUnit", { 625 | initiate: function (name) { 626 | var addEvent = function (elem, type, fn) { 627 | if (elem.addEventListener) { 628 | elem.addEventListener(type, fn, false); 629 | } else if (elem.attachEvent) { 630 | elem.attachEvent("on" + type, fn); 631 | } 632 | }; 633 | 634 | // after suite loads, set the header on the report page 635 | addEvent(window,'load',function () { 636 | // document.getElementsByTag('h1').innerHTML = name; 637 | var h1s = document.getElementsByTagName('h1'); 638 | if (h1s.length > 0) { 639 | h1s[0].innerHTML = name; 640 | } 641 | }); 642 | }, 643 | /** 644 | * Implements assert against QUnit's `ok` 645 | */ 646 | assert: function (expr, msg) { 647 | ok(expr, msg); 648 | }, 649 | /** 650 | * Implements pause against QUnit's stop() 651 | */ 652 | pause: function () { 653 | stop(); 654 | }, 655 | /** 656 | * Implements resume against QUnit's start() 657 | */ 658 | resume: function () { 659 | start(); 660 | }, 661 | /** 662 | * Compiles nested set of examples into flat array of QUnit statements 663 | * returned bound up in a single callable function 664 | * @param {Array} examples Array of possibly nested Example instances 665 | * @returns function of which, when called, will execute all translated QUnit statements 666 | */ 667 | compile: function (name, examples) { 668 | var statements = [], 669 | each = pavlov.util.each; 670 | 671 | /** 672 | * Comples a single example and its children into QUnit statements 673 | * @param {Example} example Single example instance 674 | * possibly with nested instances 675 | */ 676 | var compileDescription = function (example) { 677 | 678 | // get before and after rollups 679 | var befores = example.befores(), 680 | afters = example.afters(); 681 | 682 | // create a module with setup and teardown 683 | // that executes all current befores/afters 684 | statements.push(function () { 685 | module(example.names(), { 686 | setup: function () { 687 | each(befores, function () { this(); }); 688 | }, 689 | teardown: function () { 690 | each(afters, function () { this(); }); 691 | } 692 | }); 693 | }); 694 | 695 | // create a test for each spec/"it" in the example 696 | each(example.specs, function () { 697 | var spec = this; 698 | statements.push(function () { 699 | test(spec[0],spec[1]); 700 | }); 701 | }); 702 | 703 | // recurse through example's nested examples 704 | each(example.children, function () { 705 | compileDescription(this); 706 | }); 707 | }; 708 | 709 | // compile all root examples 710 | each(examples, function () { 711 | compileDescription(this, statements); 712 | }); 713 | 714 | // return a single function which, when called, 715 | // executes all qunit statements 716 | return function () { 717 | each(statements, function () { this(); }); 718 | }; 719 | } 720 | }); 721 | 722 | pavlov.extendAssertions({ 723 | /** 724 | * Asserts two objects are deeply equivalent, proxying QUnit's deepEqual assertion 725 | */ 726 | isSameAs: function (actual, expected, message) { 727 | deepEqual(actual, expected, message); 728 | }, 729 | /* 730 | * Asserts two objects are deeply in-equivalent, proxying QUnit's notDeepEqual assertion 731 | */ 732 | isNotSameAs: function (actual, expected, message) { 733 | notDeepEqual(actual, expected, message); 734 | } 735 | }); 736 | 737 | // alias pavlov.specify as QUnit.specify for legacy support 738 | QUnit.specify = pavlov.specify; 739 | pavlov.util.extend(QUnit.specify, pavlov); 740 | }()); 741 | --------------------------------------------------------------------------------