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