├── .gitignore ├── Makefile ├── README.mkd ├── ref ├── bigpipe.js └── usage.js ├── src ├── bigpipe.js └── pagelet.js └── tests ├── assets ├── jasmine-html.js ├── jasmine-jquery.js ├── jasmine.css ├── jasmine.js ├── jquery-1.6.js ├── jquery.ui.core.js ├── jquery.ui.widget.js └── json2.js ├── runner.html └── specs ├── bigpipe-spec.js └── pagelet-spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | python -m SimpleHTTPServer 8000 3 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | An open-source Facebook like BigPipe implementation. 4 | 5 | ## Development 6 | 7 | To run all tests is simple. Go to the project main path and run: 8 | 9 | $ make test 10 | 11 | Then open your prefered web browser and access: 12 | 13 | http://localhost:8000/tests/runner.html 14 | 15 | ## Reference 16 | 17 | * BigPipe: Pipelining web pages for high performance - https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919 18 | * This project is based on another project that uses Prototype Framework - https://github.com/dynamoid/bigpipe-abandoned 19 | 20 | ## Contributions 21 | 22 | * Rafael Caricio - https://github.com/rafaelcaricio 23 | * Bernardo Heynemann - https://github.com/heynemann 24 | * Adones Cunha - https://github.com/adonescunha 25 | -------------------------------------------------------------------------------- /ref/bigpipe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Bigpipe JS 4 | * https://github.com/adonescunha/bigpipe 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license 8 | * Copyright (c) 2011 Adones Cunha adones@atepassar.com 9 | * 10 | */ 11 | 12 | var PageletResource = function(name, pagelet) { 13 | var that = {}; 14 | that.name = name; 15 | that.pagelet = pagelet; 16 | that.phase = 0; 17 | 18 | return that; 19 | }; 20 | 21 | var CSSResource = function(name, pagelet) { 22 | var that = PageletResource(name, pagelet); 23 | 24 | that.load = function() { 25 | if (this.phase != 0) { 26 | return; 27 | } 28 | 29 | BigPipe.debug('Pagelet ' + this.pagelet.id + 30 | ' CSS resource started to load: ' + this.name); 31 | 32 | var $link = $(document.createElement('link')); 33 | $link.attr('rel', 'stylesheet') 34 | .attr('type', 'text/css') 35 | .attr('href', this.name); 36 | $('head').append($link); 37 | this.phase = 1; 38 | that.pagelet.onCSSLoad(this); 39 | }; 40 | 41 | return that; 42 | }; 43 | 44 | var JSResource = function(name, pagelet) { 45 | var that = PageletResource(name, pagelet); 46 | 47 | that.load = function() { 48 | if (this.phase != 0) { 49 | return; 50 | } 51 | 52 | BigPipe.debug('Pagelet ' + this.pagelet.id + 53 | ' JS resource started to load: ' + this.name); 54 | 55 | var $script = $(document.createElement('script')); 56 | $script.attr('type', 'text/javascript').attr('src', this.name); 57 | $('head').append($script); 58 | that.pagelet.onJSLoad(this); 59 | }; 60 | 61 | return that; 62 | }; 63 | 64 | var Pagelet = function(id, cssResources, jsResources, html) { 65 | var that = {}; 66 | 67 | that.id = id; 68 | that.cssResources = cssResources.map(function(e) { 69 | return CSSResource(e, that); 70 | }); 71 | that.jsResources = jsResources.map(function(e) { 72 | return JSResource(e, that); 73 | }); 74 | that.html = html; 75 | 76 | that.start = function() { 77 | for each (var css in this.cssResources) { 78 | this.phase = 1; 79 | css.load(); 80 | } 81 | 82 | if (this.phase == 0) { 83 | this.injectHTML(); 84 | } 85 | }; 86 | 87 | that.injectHTML = function() { 88 | this.phase = 2; 89 | $('#' + this.id).html(this.html); 90 | this.phase = 3; 91 | BigPipe.pageletHTMLInjected(this); 92 | }; 93 | 94 | that.onCSSLoad = function(css) { 95 | if (css.phase == 2) { 96 | return; 97 | } 98 | 99 | BigPipe.debug('Pagelet ' + this.id + ' CSS resource is loaded: ' + css.name); 100 | 101 | css.phase = 2; 102 | 103 | var allLoaded = this.cssResources.reduce(function(a, b) { 104 | return a && b.phase == 2; 105 | }, true); 106 | 107 | if (!allLoaded) { 108 | return; 109 | } 110 | 111 | BigPipe.debug('Pagelet ' + this.id + ': All CSS resources are loaded'); 112 | this.injectHTML(); 113 | }; 114 | 115 | that.onJSLoad = function(js) { 116 | if (js != undefined) { 117 | BigPipe.debug('Pagelet ' + this.id + ' JS resource is loaded: ' + js.name); 118 | } 119 | 120 | if (this.phase > 3) { 121 | return; 122 | } 123 | 124 | js.phase = 2; 125 | 126 | var allLoaded = this.jsResources.reduce(function(a, b) { 127 | return a && b.phase == 2; 128 | }, true); 129 | 130 | if (!allLoaded) { 131 | return; 132 | } 133 | 134 | if (this.jsResources > 0) { 135 | BigPipe.debug('Pagelet ' + this.id + ': All JS resources are loaded'); 136 | } 137 | 138 | this.phase = 4; 139 | }; 140 | 141 | return that; 142 | }; 143 | 144 | var BigPipe = { 145 | pagelets: [], 146 | phase: 0, 147 | 148 | debug: function(msg) { 149 | if (typeof console != undefined && typeof console.log != undefined) { 150 | console.log(msg); 151 | } 152 | }, 153 | 154 | onPageletArrive: function(json) { 155 | this.debug('Pagelet arrived: ' + json); 156 | 157 | if (json.isLast != undefined && json.isLast) { 158 | this.phase = 1; 159 | } 160 | 161 | var pagelet = Pagelet(json.id, json.css, json.js, json.content); 162 | this.pagelets.push(pagelet); 163 | pagelet.start(); 164 | }, 165 | 166 | pageletHTMLInjected: function(pagelet) { 167 | this.debug('Pagelet HTML injected: ' + pagelet.id); 168 | 169 | var allLoaded = this.pagelets.reduce(function(a, b) { 170 | return a && b.phase >= 3; 171 | }, true); 172 | 173 | if (!allLoaded) { 174 | return; 175 | } 176 | 177 | if (this.phase == 1) { 178 | this.loadJSResources(); 179 | } 180 | }, 181 | 182 | loadJSResources: function() { 183 | this.debug('Starting to load JS resources...'); 184 | 185 | this.phase = 2; 186 | var something_loaded = false; 187 | 188 | for each (var pagelet in this.pagelets) { 189 | for each (var js in pagelet.jsResources) { 190 | something_loaded = true; 191 | js.load(); 192 | } 193 | 194 | if (pagelet.jsResources.length == 0) { 195 | pagelet.onJSLoad(); 196 | } 197 | } 198 | 199 | if (!something_loaded) { 200 | this.debug('No JS resources to load.'); 201 | } 202 | }, 203 | }; 204 | 205 | -------------------------------------------------------------------------------- /ref/usage.js: -------------------------------------------------------------------------------- 1 | var bigpipe; 2 | 3 | var pagelets = [ 4 | { 5 | key: 'else', 6 | container: "#timeline", // always an object id 7 | url: "/home#!pagelet=timeline", 8 | css: ["/media/common.css", "/media/css/timeline.css"], 9 | js: ["a.js", "b.js", "c.js"], 10 | contentDelay: 30 11 | }, 12 | { 13 | key: 'whatever', 14 | container: "#friends_box", 15 | content: "
Hello World
", //Content to be placed directly in the element's innerHTML 16 | css: ["/media/css/friends_box.css"], 17 | js: ["a.js", "d.js", "e.js"], 18 | onContentLoaded: function() { //Markup and CSS loaded 19 | bigpipe.loadPagelet('friends_box'); 20 | } 21 | }, 22 | { 23 | key: 'friends_box', 24 | container: "#friends_box", 25 | url: "/home#!pagelet=timeline", 26 | css: ["/media/css/friends_box.css"], 27 | js: ["a.js", "j.js"], 28 | deferred: true //Should not load until someone calls it 29 | } 30 | ]; 31 | 32 | bigpipe = new Bigpipe(pagelets, { 33 | onLoad: function() { 34 | // all content and scripts loaded 35 | } 36 | }); 37 | 38 | // Order of loading Javascript files for the above scenario 39 | // a - 3 + 3 = 6 40 | // j - 1 41 | // b - 2 42 | // c - 1 43 | // d - 2 44 | // e - 1 45 | 46 | // a, [b,d], [c,e,j] 47 | 48 | -------------------------------------------------------------------------------- /src/bigpipe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Bigpipe JS 4 | * https://github.com/orygens/bigpipe-js 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license 8 | * Copyright (c) 2011 Orygens contato@orygens.com 9 | * 10 | */ 11 | 12 | (function($) { 13 | 14 | var pagelets = [], 15 | callbacks = {}; 16 | 17 | var jQueryBigPipe = function(pagelets, callbacks) { 18 | 19 | }; 20 | 21 | jQueryBigPipe.loadPagelet = function(key) { 22 | $.error("Not yet implemented!"); 23 | }; 24 | 25 | $.extend({ 26 | bigPipe: jQueryBigPipe 27 | }); 28 | 29 | $.widget("bigpipe.BigPipe", { 30 | 31 | options: { 32 | pagelets: [] 33 | }, 34 | 35 | _create: function() { 36 | 37 | this.pageletCount = this.options.pagelets.length; 38 | 39 | $.each(this.options.pagelets, $.proxy(function() { 40 | this.element.Pagelet({ 41 | id: this.id, 42 | content: this.content, 43 | css: this.css, 44 | js: this.js 45 | }); 46 | }, this)); 47 | 48 | this._bindEvents(); 49 | 50 | this.element.trigger("loadCSS"); 51 | }, 52 | 53 | _bindEvents: function() { 54 | this.element.bind("cssLoaded.BigPipe", this._afterAll(this._cssLoaded)); 55 | this.element.bind("contentLoaded.BigPipe", this._afterAll(this._contentLoaded)); 56 | this.element.bind("jsLoaded.BigPipe", this._afterAll(this._jsLoaded)); 57 | }, 58 | 59 | _afterAll: function(handler) { 60 | var counter = this.pageletCount; 61 | return $.proxy(function(ev) { 62 | counter -= 1; 63 | if (counter <= 0) { 64 | handler.call(this); 65 | } 66 | }, this); 67 | }, 68 | 69 | _cssLoaded: function() { 70 | this.element.trigger("loadContent"); 71 | }, 72 | 73 | _contentLoaded: function() { 74 | this.element.trigger("loadJS"); 75 | }, 76 | 77 | _jsLoaded: function() { 78 | this.element.trigger("onLoad"); 79 | this.element.unbind(".BigPipe"); 80 | } 81 | 82 | }); 83 | 84 | })(jQuery); 85 | -------------------------------------------------------------------------------- /src/pagelet.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Bigpipe JS 4 | * https://github.com/orygens/bigpipe-js 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license 8 | * Copyright (c) 2011 Orygens contato@orygens.com 9 | * 10 | */ 11 | 12 | (function($) { 13 | 14 | $.widget("bigpipe.Pagelet", { 15 | 16 | options: { 17 | id: "", 18 | content: null, 19 | url: null, 20 | css: [], 21 | js: [] 22 | }, 23 | 24 | _create: function() { 25 | this.element.bind("loadCSS.BigPipe", $.proxy(this._loadCSS, this)); 26 | this.element.bind("loadContent.BigPipe", $.proxy(this._loadContent, this)); 27 | this.element.bind("loadJS.BigPipe", $.proxy(this._loadJS, this)); 28 | }, 29 | 30 | _loadCSS: function() { 31 | $.each(this.options.css, $.proxy(function(cssFile) { 32 | this.element.trigger("loadCSSResource", cssFile); 33 | }, this)); 34 | 35 | this.element.trigger("cssLoaded"); 36 | }, 37 | 38 | _loadContent: function() { 39 | var loadContent = function(content) { 40 | this.element.find("#" + this.options.id).html(content); 41 | this.element.trigger("contentLoaded"); 42 | } 43 | 44 | if (this.options.content) { 45 | loadContent(this.options.content); 46 | } else if (this.options.url) { 47 | $.ajax({ 48 | url: this.options.url, 49 | success: $.proxy(loadContent, this) 50 | }); 51 | } 52 | }, 53 | 54 | _loadJS: function() { 55 | $.each(this.options.js, $.proxy(function(jsFile) { 56 | this.element.trigger("loadJSResource", jsFile); 57 | }, this)); 58 | 59 | this.element.trigger("jsLoaded"); 60 | } 61 | 62 | }); 63 | 64 | })(jQuery); 65 | -------------------------------------------------------------------------------- /tests/assets/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.TrivialReporter = function(doc) { 2 | this.document = doc || document; 3 | this.suiteDivs = {}; 4 | this.logRunningSpecs = false; 5 | }; 6 | 7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 8 | var el = document.createElement(type); 9 | 10 | for (var i = 2; i < arguments.length; i++) { 11 | var child = arguments[i]; 12 | 13 | if (typeof child === 'string') { 14 | el.appendChild(document.createTextNode(child)); 15 | } else { 16 | if (child) { el.appendChild(child); } 17 | } 18 | } 19 | 20 | for (var attr in attrs) { 21 | if (attr == "className") { 22 | el[attr] = attrs[attr]; 23 | } else { 24 | el.setAttribute(attr, attrs[attr]); 25 | } 26 | } 27 | 28 | return el; 29 | }; 30 | 31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 32 | var showPassed, showSkipped; 33 | 34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 35 | this.createDom('div', { className: 'banner' }, 36 | this.createDom('div', { className: 'logo' }, 37 | this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"), 38 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 39 | this.createDom('div', { className: 'options' }, 40 | "Show ", 41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 45 | ) 46 | ), 47 | 48 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 52 | ); 53 | 54 | this.document.body.appendChild(this.outerDiv); 55 | 56 | var suites = runner.suites(); 57 | for (var i = 0; i < suites.length; i++) { 58 | var suite = suites[i]; 59 | var suiteDiv = this.createDom('div', { className: 'suite' }, 60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 62 | this.suiteDivs[suite.id] = suiteDiv; 63 | var parentDiv = this.outerDiv; 64 | if (suite.parentSuite) { 65 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 66 | } 67 | parentDiv.appendChild(suiteDiv); 68 | } 69 | 70 | this.startedAt = new Date(); 71 | 72 | var self = this; 73 | showPassed.onclick = function(evt) { 74 | if (showPassed.checked) { 75 | self.outerDiv.className += ' show-passed'; 76 | } else { 77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 78 | } 79 | }; 80 | 81 | showSkipped.onclick = function(evt) { 82 | if (showSkipped.checked) { 83 | self.outerDiv.className += ' show-skipped'; 84 | } else { 85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 86 | } 87 | }; 88 | }; 89 | 90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 91 | var results = runner.results(); 92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 93 | this.runnerDiv.setAttribute("class", className); 94 | //do it twice for IE 95 | this.runnerDiv.setAttribute("className", className); 96 | var specs = runner.specs(); 97 | var specCount = 0; 98 | for (var i = 0; i < specs.length; i++) { 99 | if (this.specFilter(specs[i])) { 100 | specCount++; 101 | } 102 | } 103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 106 | 107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 108 | }; 109 | 110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 111 | var results = suite.results(); 112 | var status = results.passed() ? 'passed' : 'failed'; 113 | if (results.totalCount === 0) { // todo: change this to check results.skipped 114 | status = 'skipped'; 115 | } 116 | this.suiteDivs[suite.id].className += " " + status; 117 | }; 118 | 119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 120 | if (this.logRunningSpecs) { 121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 122 | } 123 | }; 124 | 125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 126 | var results = spec.results(); 127 | var status = results.passed() ? 'passed' : 'failed'; 128 | if (results.skipped) { 129 | status = 'skipped'; 130 | } 131 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 133 | this.createDom('a', { 134 | className: 'description', 135 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 136 | title: spec.getFullName() 137 | }, spec.description)); 138 | 139 | 140 | var resultItems = results.getItems(); 141 | var messagesDiv = this.createDom('div', { className: 'messages' }); 142 | for (var i = 0; i < resultItems.length; i++) { 143 | var result = resultItems[i]; 144 | 145 | if (result.type == 'log') { 146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 147 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 149 | 150 | if (result.trace.stack) { 151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 152 | } 153 | } 154 | } 155 | 156 | if (messagesDiv.childNodes.length > 0) { 157 | specDiv.appendChild(messagesDiv); 158 | } 159 | 160 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 161 | }; 162 | 163 | jasmine.TrivialReporter.prototype.log = function() { 164 | var console = jasmine.getGlobal().console; 165 | if (console && console.log) { 166 | if (console.log.apply) { 167 | console.log.apply(console, arguments); 168 | } else { 169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 170 | } 171 | } 172 | }; 173 | 174 | jasmine.TrivialReporter.prototype.getLocation = function() { 175 | return this.document.location; 176 | }; 177 | 178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 179 | var paramMap = {}; 180 | var params = this.getLocation().search.substring(1).split('&'); 181 | for (var i = 0; i < params.length; i++) { 182 | var p = params[i].split('='); 183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 184 | } 185 | 186 | if (!paramMap.spec) { 187 | return true; 188 | } 189 | return spec.getFullName().indexOf(paramMap.spec) === 0; 190 | }; 191 | -------------------------------------------------------------------------------- /tests/assets/jasmine-jquery.js: -------------------------------------------------------------------------------- 1 | var readFixtures = function() { 2 | return jasmine.getFixtures().proxyCallTo_('read', arguments); 3 | }; 4 | 5 | var loadFixtures = function() { 6 | jasmine.getFixtures().proxyCallTo_('load', arguments); 7 | }; 8 | 9 | var setFixtures = function(html) { 10 | jasmine.getFixtures().set(html); 11 | }; 12 | 13 | var sandbox = function(attributes) { 14 | return jasmine.getFixtures().sandbox(attributes); 15 | }; 16 | 17 | var spyOnEvent = function(selector, eventName) { 18 | jasmine.JQuery.events.spyOn(selector, eventName); 19 | } 20 | 21 | jasmine.getFixtures = function() { 22 | return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures(); 23 | }; 24 | 25 | jasmine.Fixtures = function() { 26 | this.containerId = 'jasmine-fixtures'; 27 | this.fixturesCache_ = {}; 28 | this.fixturesPath = 'spec/javascripts/fixtures'; 29 | }; 30 | 31 | jasmine.Fixtures.prototype.set = function(html) { 32 | this.cleanUp(); 33 | this.createContainer_(html); 34 | }; 35 | 36 | jasmine.Fixtures.prototype.load = function() { 37 | this.cleanUp(); 38 | this.createContainer_(this.read.apply(this, arguments)); 39 | }; 40 | 41 | jasmine.Fixtures.prototype.read = function() { 42 | var htmlChunks = []; 43 | 44 | var fixtureUrls = arguments; 45 | for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { 46 | htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])); 47 | } 48 | 49 | return htmlChunks.join(''); 50 | }; 51 | 52 | jasmine.Fixtures.prototype.clearCache = function() { 53 | this.fixturesCache_ = {}; 54 | }; 55 | 56 | jasmine.Fixtures.prototype.cleanUp = function() { 57 | $('#' + this.containerId).remove(); 58 | }; 59 | 60 | jasmine.Fixtures.prototype.sandbox = function(attributes) { 61 | var attributesToSet = attributes || {}; 62 | return $('
').attr(attributesToSet); 63 | }; 64 | 65 | jasmine.Fixtures.prototype.createContainer_ = function(html) { 66 | var container = $('
'); 67 | container.html(html); 68 | $('body').append(container); 69 | }; 70 | 71 | jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) { 72 | if (typeof this.fixturesCache_[url] == 'undefined') { 73 | this.loadFixtureIntoCache_(url); 74 | } 75 | return this.fixturesCache_[url]; 76 | }; 77 | 78 | jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) { 79 | var self = this; 80 | var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl; 81 | $.ajax({ 82 | async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded 83 | cache: false, 84 | dataType: 'html', 85 | url: url, 86 | success: function(data) { 87 | self.fixturesCache_[relativeUrl] = data; 88 | } 89 | }); 90 | }; 91 | 92 | jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) { 93 | return this[methodName].apply(this, passedArguments); 94 | }; 95 | 96 | 97 | jasmine.JQuery = function() {}; 98 | 99 | jasmine.JQuery.browserTagCaseIndependentHtml = function(html) { 100 | return $('
').append(html).html(); 101 | }; 102 | 103 | jasmine.JQuery.elementToString = function(element) { 104 | return $('
').append(element.clone()).html(); 105 | }; 106 | 107 | jasmine.JQuery.matchersClass = {}; 108 | 109 | (function(namespace) { 110 | var data = { 111 | spiedEvents: {}, 112 | handlers: [] 113 | }; 114 | 115 | namespace.events = { 116 | spyOn: function(selector, eventName) { 117 | var handler = function(e) { 118 | data.spiedEvents[[selector, eventName]] = e; 119 | }; 120 | $(selector).bind(eventName, handler); 121 | data.handlers.push(handler); 122 | }, 123 | 124 | wasTriggered: function(selector, eventName) { 125 | return !!(data.spiedEvents[[selector, eventName]]); 126 | }, 127 | 128 | cleanUp: function() { 129 | data.spiedEvents = {}; 130 | data.handlers = []; 131 | } 132 | } 133 | })(jasmine.JQuery); 134 | 135 | (function(){ 136 | var jQueryMatchers = { 137 | toHaveClass: function(className) { 138 | return this.actual.hasClass(className); 139 | }, 140 | 141 | toBeVisible: function() { 142 | return this.actual.is(':visible'); 143 | }, 144 | 145 | toBeHidden: function() { 146 | return this.actual.is(':hidden'); 147 | }, 148 | 149 | toBeSelected: function() { 150 | return this.actual.is(':selected'); 151 | }, 152 | 153 | toBeChecked: function() { 154 | return this.actual.is(':checked'); 155 | }, 156 | 157 | toBeEmpty: function() { 158 | return this.actual.is(':empty'); 159 | }, 160 | 161 | toExist: function() { 162 | return this.actual.size() > 0; 163 | }, 164 | 165 | toHaveAttr: function(attributeName, expectedAttributeValue) { 166 | return hasProperty(this.actual.attr(attributeName), expectedAttributeValue); 167 | }, 168 | 169 | toHaveId: function(id) { 170 | return this.actual.attr('id') == id; 171 | }, 172 | 173 | toHaveHtml: function(html) { 174 | return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html); 175 | }, 176 | 177 | toHaveText: function(text) { 178 | if (text && jQuery.isFunction(text.test)) { 179 | return text.test(this.actual.text()); 180 | } else { 181 | return this.actual.text() == text; 182 | } 183 | }, 184 | 185 | toHaveValue: function(value) { 186 | return this.actual.val() == value; 187 | }, 188 | 189 | toHaveData: function(key, expectedValue) { 190 | return hasProperty(this.actual.data(key), expectedValue); 191 | }, 192 | 193 | toBe: function(selector) { 194 | return this.actual.is(selector); 195 | }, 196 | 197 | toContain: function(selector) { 198 | return this.actual.find(selector).size() > 0; 199 | }, 200 | 201 | toBeDisabled: function(selector){ 202 | return this.actual.is(':disabled'); 203 | }, 204 | 205 | // tests the existence of a specific event binding 206 | toHandle: function(eventName) { 207 | var events = this.actual.data("events"); 208 | return events && events[eventName].length > 0; 209 | }, 210 | 211 | // tests the existence of a specific event binding + handler 212 | toHandleWith: function(eventName, eventHandler) { 213 | var stack = this.actual.data("events")[eventName]; 214 | var i; 215 | for (i = 0; i < stack.length; i++) { 216 | if (stack[i].handler == eventHandler) { 217 | return true; 218 | } 219 | } 220 | return false; 221 | } 222 | }; 223 | 224 | var hasProperty = function(actualValue, expectedValue) { 225 | if (expectedValue === undefined) { 226 | return actualValue !== undefined; 227 | } 228 | return actualValue == expectedValue; 229 | }; 230 | 231 | var bindMatcher = function(methodName) { 232 | var builtInMatcher = jasmine.Matchers.prototype[methodName]; 233 | 234 | jasmine.JQuery.matchersClass[methodName] = function() { 235 | if (this.actual instanceof jQuery) { 236 | var result = jQueryMatchers[methodName].apply(this, arguments); 237 | this.actual = jasmine.JQuery.elementToString(this.actual); 238 | return result; 239 | } 240 | 241 | if (builtInMatcher) { 242 | return builtInMatcher.apply(this, arguments); 243 | } 244 | 245 | return false; 246 | }; 247 | }; 248 | 249 | for(var methodName in jQueryMatchers) { 250 | bindMatcher(methodName); 251 | } 252 | })(); 253 | 254 | beforeEach(function() { 255 | this.addMatchers(jasmine.JQuery.matchersClass); 256 | this.addMatchers({ 257 | toHaveBeenTriggeredOn: function(selector) { 258 | this.message = function() { 259 | return [ 260 | "Expected event " + this.actual + " to have been triggered on" + selector, 261 | "Expected event " + this.actual + " not to have been triggered on" + selector 262 | ]; 263 | }; 264 | return jasmine.JQuery.events.wasTriggered(selector, this.actual); 265 | } 266 | }) 267 | }); 268 | 269 | afterEach(function() { 270 | jasmine.getFixtures().cleanUp(); 271 | jasmine.JQuery.events.cleanUp(); 272 | }); 273 | -------------------------------------------------------------------------------- /tests/assets/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /tests/assets/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 | jasmine.getGlobal = function() { 43 | function getGlobal() { 44 | return this; 45 | } 46 | 47 | return getGlobal(); 48 | }; 49 | 50 | /** 51 | * Allows for bound functions to be compared. Internal use only. 52 | * 53 | * @ignore 54 | * @private 55 | * @param base {Object} bound 'this' for the function 56 | * @param name {Function} function to find 57 | */ 58 | jasmine.bindOriginal_ = function(base, name) { 59 | var original = base[name]; 60 | if (original.apply) { 61 | return function() { 62 | return original.apply(base, arguments); 63 | }; 64 | } else { 65 | // IE support 66 | return jasmine.getGlobal()[name]; 67 | } 68 | }; 69 | 70 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 71 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 72 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 73 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 74 | 75 | jasmine.MessageResult = function(values) { 76 | this.type = 'log'; 77 | this.values = values; 78 | this.trace = new Error(); // todo: test better 79 | }; 80 | 81 | jasmine.MessageResult.prototype.toString = function() { 82 | var text = ""; 83 | for(var i = 0; i < this.values.length; i++) { 84 | if (i > 0) text += " "; 85 | if (jasmine.isString_(this.values[i])) { 86 | text += this.values[i]; 87 | } else { 88 | text += jasmine.pp(this.values[i]); 89 | } 90 | } 91 | return text; 92 | }; 93 | 94 | jasmine.ExpectationResult = function(params) { 95 | this.type = 'expect'; 96 | this.matcherName = params.matcherName; 97 | this.passed_ = params.passed; 98 | this.expected = params.expected; 99 | this.actual = params.actual; 100 | 101 | this.message = this.passed_ ? 'Passed.' : params.message; 102 | this.trace = this.passed_ ? '' : new Error(this.message); 103 | }; 104 | 105 | jasmine.ExpectationResult.prototype.toString = function () { 106 | return this.message; 107 | }; 108 | 109 | jasmine.ExpectationResult.prototype.passed = function () { 110 | return this.passed_; 111 | }; 112 | 113 | /** 114 | * Getter for the Jasmine environment. Ensures one gets created 115 | */ 116 | jasmine.getEnv = function() { 117 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 118 | return env; 119 | }; 120 | 121 | /** 122 | * @ignore 123 | * @private 124 | * @param value 125 | * @returns {Boolean} 126 | */ 127 | jasmine.isArray_ = function(value) { 128 | return jasmine.isA_("Array", value); 129 | }; 130 | 131 | /** 132 | * @ignore 133 | * @private 134 | * @param value 135 | * @returns {Boolean} 136 | */ 137 | jasmine.isString_ = function(value) { 138 | return jasmine.isA_("String", value); 139 | }; 140 | 141 | /** 142 | * @ignore 143 | * @private 144 | * @param value 145 | * @returns {Boolean} 146 | */ 147 | jasmine.isNumber_ = function(value) { 148 | return jasmine.isA_("Number", value); 149 | }; 150 | 151 | /** 152 | * @ignore 153 | * @private 154 | * @param {String} typeName 155 | * @param value 156 | * @returns {Boolean} 157 | */ 158 | jasmine.isA_ = function(typeName, value) { 159 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 160 | }; 161 | 162 | /** 163 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 164 | * 165 | * @param value {Object} an object to be outputted 166 | * @returns {String} 167 | */ 168 | jasmine.pp = function(value) { 169 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 170 | stringPrettyPrinter.format(value); 171 | return stringPrettyPrinter.string; 172 | }; 173 | 174 | /** 175 | * Returns true if the object is a DOM Node. 176 | * 177 | * @param {Object} obj object to check 178 | * @returns {Boolean} 179 | */ 180 | jasmine.isDomNode = function(obj) { 181 | return obj.nodeType > 0; 182 | }; 183 | 184 | /** 185 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 186 | * 187 | * @example 188 | * // don't care about which function is passed in, as long as it's a function 189 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 190 | * 191 | * @param {Class} clazz 192 | * @returns matchable object of the type clazz 193 | */ 194 | jasmine.any = function(clazz) { 195 | return new jasmine.Matchers.Any(clazz); 196 | }; 197 | 198 | /** 199 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 200 | * 201 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 202 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 203 | * 204 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 205 | * 206 | * Spies are torn down at the end of every spec. 207 | * 208 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 209 | * 210 | * @example 211 | * // a stub 212 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 213 | * 214 | * // spy example 215 | * var foo = { 216 | * not: function(bool) { return !bool; } 217 | * } 218 | * 219 | * // actual foo.not will not be called, execution stops 220 | * spyOn(foo, 'not'); 221 | 222 | // foo.not spied upon, execution will continue to implementation 223 | * spyOn(foo, 'not').andCallThrough(); 224 | * 225 | * // fake example 226 | * var foo = { 227 | * not: function(bool) { return !bool; } 228 | * } 229 | * 230 | * // foo.not(val) will return val 231 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 232 | * 233 | * // mock example 234 | * foo.not(7 == 7); 235 | * expect(foo.not).toHaveBeenCalled(); 236 | * expect(foo.not).toHaveBeenCalledWith(true); 237 | * 238 | * @constructor 239 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 240 | * @param {String} name 241 | */ 242 | jasmine.Spy = function(name) { 243 | /** 244 | * The name of the spy, if provided. 245 | */ 246 | this.identity = name || 'unknown'; 247 | /** 248 | * Is this Object a spy? 249 | */ 250 | this.isSpy = true; 251 | /** 252 | * The actual function this spy stubs. 253 | */ 254 | this.plan = function() { 255 | }; 256 | /** 257 | * Tracking of the most recent call to the spy. 258 | * @example 259 | * var mySpy = jasmine.createSpy('foo'); 260 | * mySpy(1, 2); 261 | * mySpy.mostRecentCall.args = [1, 2]; 262 | */ 263 | this.mostRecentCall = {}; 264 | 265 | /** 266 | * Holds arguments for each call to the spy, indexed by call count 267 | * @example 268 | * var mySpy = jasmine.createSpy('foo'); 269 | * mySpy(1, 2); 270 | * mySpy(7, 8); 271 | * mySpy.mostRecentCall.args = [7, 8]; 272 | * mySpy.argsForCall[0] = [1, 2]; 273 | * mySpy.argsForCall[1] = [7, 8]; 274 | */ 275 | this.argsForCall = []; 276 | this.calls = []; 277 | }; 278 | 279 | /** 280 | * Tells a spy to call through to the actual implemenatation. 281 | * 282 | * @example 283 | * var foo = { 284 | * bar: function() { // do some stuff } 285 | * } 286 | * 287 | * // defining a spy on an existing property: foo.bar 288 | * spyOn(foo, 'bar').andCallThrough(); 289 | */ 290 | jasmine.Spy.prototype.andCallThrough = function() { 291 | this.plan = this.originalValue; 292 | return this; 293 | }; 294 | 295 | /** 296 | * For setting the return value of a spy. 297 | * 298 | * @example 299 | * // defining a spy from scratch: foo() returns 'baz' 300 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 301 | * 302 | * // defining a spy on an existing property: foo.bar() returns 'baz' 303 | * spyOn(foo, 'bar').andReturn('baz'); 304 | * 305 | * @param {Object} value 306 | */ 307 | jasmine.Spy.prototype.andReturn = function(value) { 308 | this.plan = function() { 309 | return value; 310 | }; 311 | return this; 312 | }; 313 | 314 | /** 315 | * For throwing an exception when a spy is called. 316 | * 317 | * @example 318 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 319 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 320 | * 321 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 322 | * spyOn(foo, 'bar').andThrow('baz'); 323 | * 324 | * @param {String} exceptionMsg 325 | */ 326 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 327 | this.plan = function() { 328 | throw exceptionMsg; 329 | }; 330 | return this; 331 | }; 332 | 333 | /** 334 | * Calls an alternate implementation when a spy is called. 335 | * 336 | * @example 337 | * var baz = function() { 338 | * // do some stuff, return something 339 | * } 340 | * // defining a spy from scratch: foo() calls the function baz 341 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 342 | * 343 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 344 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 345 | * 346 | * @param {Function} fakeFunc 347 | */ 348 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 349 | this.plan = fakeFunc; 350 | return this; 351 | }; 352 | 353 | /** 354 | * Resets all of a spy's the tracking variables so that it can be used again. 355 | * 356 | * @example 357 | * spyOn(foo, 'bar'); 358 | * 359 | * foo.bar(); 360 | * 361 | * expect(foo.bar.callCount).toEqual(1); 362 | * 363 | * foo.bar.reset(); 364 | * 365 | * expect(foo.bar.callCount).toEqual(0); 366 | */ 367 | jasmine.Spy.prototype.reset = function() { 368 | this.wasCalled = false; 369 | this.callCount = 0; 370 | this.argsForCall = []; 371 | this.calls = []; 372 | this.mostRecentCall = {}; 373 | }; 374 | 375 | jasmine.createSpy = function(name) { 376 | 377 | var spyObj = function() { 378 | spyObj.wasCalled = true; 379 | spyObj.callCount++; 380 | var args = jasmine.util.argsToArray(arguments); 381 | spyObj.mostRecentCall.object = this; 382 | spyObj.mostRecentCall.args = args; 383 | spyObj.argsForCall.push(args); 384 | spyObj.calls.push({object: this, args: args}); 385 | return spyObj.plan.apply(this, arguments); 386 | }; 387 | 388 | var spy = new jasmine.Spy(name); 389 | 390 | for (var prop in spy) { 391 | spyObj[prop] = spy[prop]; 392 | } 393 | 394 | spyObj.reset(); 395 | 396 | return spyObj; 397 | }; 398 | 399 | /** 400 | * Determines whether an object is a spy. 401 | * 402 | * @param {jasmine.Spy|Object} putativeSpy 403 | * @returns {Boolean} 404 | */ 405 | jasmine.isSpy = function(putativeSpy) { 406 | return putativeSpy && putativeSpy.isSpy; 407 | }; 408 | 409 | /** 410 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 411 | * large in one call. 412 | * 413 | * @param {String} baseName name of spy class 414 | * @param {Array} methodNames array of names of methods to make spies 415 | */ 416 | jasmine.createSpyObj = function(baseName, methodNames) { 417 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 418 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 419 | } 420 | var obj = {}; 421 | for (var i = 0; i < methodNames.length; i++) { 422 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 423 | } 424 | return obj; 425 | }; 426 | 427 | /** 428 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 429 | * 430 | * Be careful not to leave calls to jasmine.log in production code. 431 | */ 432 | jasmine.log = function() { 433 | var spec = jasmine.getEnv().currentSpec; 434 | spec.log.apply(spec, arguments); 435 | }; 436 | 437 | /** 438 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 439 | * 440 | * @example 441 | * // spy example 442 | * var foo = { 443 | * not: function(bool) { return !bool; } 444 | * } 445 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 446 | * 447 | * @see jasmine.createSpy 448 | * @param obj 449 | * @param methodName 450 | * @returns a Jasmine spy that can be chained with all spy methods 451 | */ 452 | var spyOn = function(obj, methodName) { 453 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 454 | }; 455 | if (isCommonJS) exports.spyOn = spyOn; 456 | 457 | /** 458 | * Creates a Jasmine spec that will be added to the current suite. 459 | * 460 | * // TODO: pending tests 461 | * 462 | * @example 463 | * it('should be true', function() { 464 | * expect(true).toEqual(true); 465 | * }); 466 | * 467 | * @param {String} desc description of this specification 468 | * @param {Function} func defines the preconditions and expectations of the spec 469 | */ 470 | var it = function(desc, func) { 471 | return jasmine.getEnv().it(desc, func); 472 | }; 473 | if (isCommonJS) exports.it = it; 474 | 475 | /** 476 | * Creates a disabled Jasmine spec. 477 | * 478 | * A convenience method that allows existing specs to be disabled temporarily during development. 479 | * 480 | * @param {String} desc description of this specification 481 | * @param {Function} func defines the preconditions and expectations of the spec 482 | */ 483 | var xit = function(desc, func) { 484 | return jasmine.getEnv().xit(desc, func); 485 | }; 486 | if (isCommonJS) exports.xit = xit; 487 | 488 | /** 489 | * Starts a chain for a Jasmine expectation. 490 | * 491 | * It is passed an Object that is the actual value and should chain to one of the many 492 | * jasmine.Matchers functions. 493 | * 494 | * @param {Object} actual Actual value to test against and expected value 495 | */ 496 | var expect = function(actual) { 497 | return jasmine.getEnv().currentSpec.expect(actual); 498 | }; 499 | if (isCommonJS) exports.expect = expect; 500 | 501 | /** 502 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 503 | * 504 | * @param {Function} func Function that defines part of a jasmine spec. 505 | */ 506 | var runs = function(func) { 507 | jasmine.getEnv().currentSpec.runs(func); 508 | }; 509 | if (isCommonJS) exports.runs = runs; 510 | 511 | /** 512 | * Waits a fixed time period before moving to the next block. 513 | * 514 | * @deprecated Use waitsFor() instead 515 | * @param {Number} timeout milliseconds to wait 516 | */ 517 | var waits = function(timeout) { 518 | jasmine.getEnv().currentSpec.waits(timeout); 519 | }; 520 | if (isCommonJS) exports.waits = waits; 521 | 522 | /** 523 | * Waits for the latchFunction to return true before proceeding to the next block. 524 | * 525 | * @param {Function} latchFunction 526 | * @param {String} optional_timeoutMessage 527 | * @param {Number} optional_timeout 528 | */ 529 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 530 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 531 | }; 532 | if (isCommonJS) exports.waitsFor = waitsFor; 533 | 534 | /** 535 | * A function that is called before each spec in a suite. 536 | * 537 | * Used for spec setup, including validating assumptions. 538 | * 539 | * @param {Function} beforeEachFunction 540 | */ 541 | var beforeEach = function(beforeEachFunction) { 542 | jasmine.getEnv().beforeEach(beforeEachFunction); 543 | }; 544 | if (isCommonJS) exports.beforeEach = beforeEach; 545 | 546 | /** 547 | * A function that is called after each spec in a suite. 548 | * 549 | * Used for restoring any state that is hijacked during spec execution. 550 | * 551 | * @param {Function} afterEachFunction 552 | */ 553 | var afterEach = function(afterEachFunction) { 554 | jasmine.getEnv().afterEach(afterEachFunction); 555 | }; 556 | if (isCommonJS) exports.afterEach = afterEach; 557 | 558 | /** 559 | * Defines a suite of specifications. 560 | * 561 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 562 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 563 | * of setup in some tests. 564 | * 565 | * @example 566 | * // TODO: a simple suite 567 | * 568 | * // TODO: a simple suite with a nested describe block 569 | * 570 | * @param {String} description A string, usually the class under test. 571 | * @param {Function} specDefinitions function that defines several specs. 572 | */ 573 | var describe = function(description, specDefinitions) { 574 | return jasmine.getEnv().describe(description, specDefinitions); 575 | }; 576 | if (isCommonJS) exports.describe = describe; 577 | 578 | /** 579 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 580 | * 581 | * @param {String} description A string, usually the class under test. 582 | * @param {Function} specDefinitions function that defines several specs. 583 | */ 584 | var xdescribe = function(description, specDefinitions) { 585 | return jasmine.getEnv().xdescribe(description, specDefinitions); 586 | }; 587 | if (isCommonJS) exports.xdescribe = xdescribe; 588 | 589 | 590 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 591 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 592 | function tryIt(f) { 593 | try { 594 | return f(); 595 | } catch(e) { 596 | } 597 | return null; 598 | } 599 | 600 | var xhr = tryIt(function(){return new ActiveXObject("Msxml2.XMLHTTP.6.0");}) || 601 | tryIt(function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0");}) || 602 | tryIt(function(){return new ActiveXObject("Msxml2.XMLHTTP");}) || 603 | tryIt(function(){return new ActiveXObject("Microsoft.XMLHTTP");}); 604 | 605 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 606 | 607 | return xhr; 608 | } : XMLHttpRequest; 609 | /** 610 | * @namespace 611 | */ 612 | jasmine.util = {}; 613 | 614 | /** 615 | * Declare that a child class inherit it's prototype from the parent class. 616 | * 617 | * @private 618 | * @param {Function} childClass 619 | * @param {Function} parentClass 620 | */ 621 | jasmine.util.inherit = function(childClass, parentClass) { 622 | /** 623 | * @private 624 | */ 625 | var subclass = function() { 626 | }; 627 | subclass.prototype = parentClass.prototype; 628 | childClass.prototype = new subclass(); 629 | }; 630 | 631 | jasmine.util.formatException = function(e) { 632 | var lineNumber; 633 | if (e.line) { 634 | lineNumber = e.line; 635 | } 636 | else if (e.lineNumber) { 637 | lineNumber = e.lineNumber; 638 | } 639 | 640 | var file; 641 | 642 | if (e.sourceURL) { 643 | file = e.sourceURL; 644 | } 645 | else if (e.fileName) { 646 | file = e.fileName; 647 | } 648 | 649 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 650 | 651 | if (file && lineNumber) { 652 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 653 | } 654 | 655 | return message; 656 | }; 657 | 658 | jasmine.util.htmlEscape = function(str) { 659 | if (!str) return str; 660 | return str.replace(/&/g, '&') 661 | .replace(//g, '>'); 663 | }; 664 | 665 | jasmine.util.argsToArray = function(args) { 666 | var arrayOfArgs = []; 667 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 668 | return arrayOfArgs; 669 | }; 670 | 671 | jasmine.util.extend = function(destination, source) { 672 | for (var property in source) destination[property] = source[property]; 673 | return destination; 674 | }; 675 | 676 | /** 677 | * Environment for Jasmine 678 | * 679 | * @constructor 680 | */ 681 | jasmine.Env = function() { 682 | this.currentSpec = null; 683 | this.currentSuite = null; 684 | this.currentRunner_ = new jasmine.Runner(this); 685 | 686 | this.reporter = new jasmine.MultiReporter(); 687 | 688 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 689 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 690 | this.lastUpdate = 0; 691 | this.specFilter = function() { 692 | return true; 693 | }; 694 | 695 | this.nextSpecId_ = 0; 696 | this.nextSuiteId_ = 0; 697 | this.equalityTesters_ = []; 698 | 699 | // wrap matchers 700 | this.matchersClass = function() { 701 | jasmine.Matchers.apply(this, arguments); 702 | }; 703 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 704 | 705 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 706 | }; 707 | 708 | 709 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 710 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 711 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 712 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 713 | 714 | /** 715 | * @returns an object containing jasmine version build info, if set. 716 | */ 717 | jasmine.Env.prototype.version = function () { 718 | if (jasmine.version_) { 719 | return jasmine.version_; 720 | } else { 721 | throw new Error('Version not set'); 722 | } 723 | }; 724 | 725 | /** 726 | * @returns string containing jasmine version build info, if set. 727 | */ 728 | jasmine.Env.prototype.versionString = function() { 729 | if (jasmine.version_) { 730 | var version = this.version(); 731 | return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; 732 | } else { 733 | return "version unknown"; 734 | } 735 | }; 736 | 737 | /** 738 | * @returns a sequential integer starting at 0 739 | */ 740 | jasmine.Env.prototype.nextSpecId = function () { 741 | return this.nextSpecId_++; 742 | }; 743 | 744 | /** 745 | * @returns a sequential integer starting at 0 746 | */ 747 | jasmine.Env.prototype.nextSuiteId = function () { 748 | return this.nextSuiteId_++; 749 | }; 750 | 751 | /** 752 | * Register a reporter to receive status updates from Jasmine. 753 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 754 | */ 755 | jasmine.Env.prototype.addReporter = function(reporter) { 756 | this.reporter.addReporter(reporter); 757 | }; 758 | 759 | jasmine.Env.prototype.execute = function() { 760 | this.currentRunner_.execute(); 761 | }; 762 | 763 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 764 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 765 | 766 | var parentSuite = this.currentSuite; 767 | if (parentSuite) { 768 | parentSuite.add(suite); 769 | } else { 770 | this.currentRunner_.add(suite); 771 | } 772 | 773 | this.currentSuite = suite; 774 | 775 | var declarationError = null; 776 | try { 777 | specDefinitions.call(suite); 778 | } catch(e) { 779 | declarationError = e; 780 | } 781 | 782 | this.currentSuite = parentSuite; 783 | 784 | if (declarationError) { 785 | this.it("encountered a declaration exception", function() { 786 | throw declarationError; 787 | }); 788 | } 789 | 790 | return suite; 791 | }; 792 | 793 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 794 | if (this.currentSuite) { 795 | this.currentSuite.beforeEach(beforeEachFunction); 796 | } else { 797 | this.currentRunner_.beforeEach(beforeEachFunction); 798 | } 799 | }; 800 | 801 | jasmine.Env.prototype.currentRunner = function () { 802 | return this.currentRunner_; 803 | }; 804 | 805 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 806 | if (this.currentSuite) { 807 | this.currentSuite.afterEach(afterEachFunction); 808 | } else { 809 | this.currentRunner_.afterEach(afterEachFunction); 810 | } 811 | 812 | }; 813 | 814 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 815 | return { 816 | execute: function() { 817 | } 818 | }; 819 | }; 820 | 821 | jasmine.Env.prototype.it = function(description, func) { 822 | var spec = new jasmine.Spec(this, this.currentSuite, description); 823 | this.currentSuite.add(spec); 824 | this.currentSpec = spec; 825 | 826 | if (func) { 827 | spec.runs(func); 828 | } 829 | 830 | return spec; 831 | }; 832 | 833 | jasmine.Env.prototype.xit = function(desc, func) { 834 | return { 835 | id: this.nextSpecId(), 836 | runs: function() { 837 | } 838 | }; 839 | }; 840 | 841 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 842 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 843 | return true; 844 | } 845 | 846 | a.__Jasmine_been_here_before__ = b; 847 | b.__Jasmine_been_here_before__ = a; 848 | 849 | var hasKey = function(obj, keyName) { 850 | return obj !== null && obj[keyName] !== jasmine.undefined; 851 | }; 852 | 853 | for (var property in b) { 854 | if (!hasKey(a, property) && hasKey(b, property)) { 855 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 856 | } 857 | } 858 | for (property in a) { 859 | if (!hasKey(b, property) && hasKey(a, property)) { 860 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 861 | } 862 | } 863 | for (property in b) { 864 | if (property == '__Jasmine_been_here_before__') continue; 865 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 866 | 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."); 867 | } 868 | } 869 | 870 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 871 | mismatchValues.push("arrays were not the same length"); 872 | } 873 | 874 | delete a.__Jasmine_been_here_before__; 875 | delete b.__Jasmine_been_here_before__; 876 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 877 | }; 878 | 879 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 880 | mismatchKeys = mismatchKeys || []; 881 | mismatchValues = mismatchValues || []; 882 | 883 | for (var i = 0; i < this.equalityTesters_.length; i++) { 884 | var equalityTester = this.equalityTesters_[i]; 885 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 886 | if (result !== jasmine.undefined) return result; 887 | } 888 | 889 | if (a === b) return true; 890 | 891 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 892 | return (a == jasmine.undefined && b == jasmine.undefined); 893 | } 894 | 895 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 896 | return a === b; 897 | } 898 | 899 | if (a instanceof Date && b instanceof Date) { 900 | return a.getTime() == b.getTime(); 901 | } 902 | 903 | if (a instanceof jasmine.Matchers.Any) { 904 | return a.matches(b); 905 | } 906 | 907 | if (b instanceof jasmine.Matchers.Any) { 908 | return b.matches(a); 909 | } 910 | 911 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 912 | return (a == b); 913 | } 914 | 915 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 916 | return (a == b); 917 | } 918 | 919 | if (typeof a === "object" && typeof b === "object") { 920 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 921 | } 922 | 923 | //Straight check 924 | return (a === b); 925 | }; 926 | 927 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 928 | if (jasmine.isArray_(haystack)) { 929 | for (var i = 0; i < haystack.length; i++) { 930 | if (this.equals_(haystack[i], needle)) return true; 931 | } 932 | return false; 933 | } 934 | return haystack.indexOf(needle) >= 0; 935 | }; 936 | 937 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 938 | this.equalityTesters_.push(equalityTester); 939 | }; 940 | /** No-op base class for Jasmine reporters. 941 | * 942 | * @constructor 943 | */ 944 | jasmine.Reporter = function() { 945 | }; 946 | 947 | //noinspection JSUnusedLocalSymbols 948 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 949 | }; 950 | 951 | //noinspection JSUnusedLocalSymbols 952 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 953 | }; 954 | 955 | //noinspection JSUnusedLocalSymbols 956 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 957 | }; 958 | 959 | //noinspection JSUnusedLocalSymbols 960 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 961 | }; 962 | 963 | //noinspection JSUnusedLocalSymbols 964 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 965 | }; 966 | 967 | //noinspection JSUnusedLocalSymbols 968 | jasmine.Reporter.prototype.log = function(str) { 969 | }; 970 | 971 | /** 972 | * Blocks are functions with executable code that make up a spec. 973 | * 974 | * @constructor 975 | * @param {jasmine.Env} env 976 | * @param {Function} func 977 | * @param {jasmine.Spec} spec 978 | */ 979 | jasmine.Block = function(env, func, spec) { 980 | this.env = env; 981 | this.func = func; 982 | this.spec = spec; 983 | }; 984 | 985 | jasmine.Block.prototype.execute = function(onComplete) { 986 | try { 987 | this.func.apply(this.spec); 988 | } catch (e) { 989 | this.spec.fail(e); 990 | } 991 | onComplete(); 992 | }; 993 | /** JavaScript API reporter. 994 | * 995 | * @constructor 996 | */ 997 | jasmine.JsApiReporter = function() { 998 | this.started = false; 999 | this.finished = false; 1000 | this.suites_ = []; 1001 | this.results_ = {}; 1002 | }; 1003 | 1004 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1005 | this.started = true; 1006 | var suites = runner.topLevelSuites(); 1007 | for (var i = 0; i < suites.length; i++) { 1008 | var suite = suites[i]; 1009 | this.suites_.push(this.summarize_(suite)); 1010 | } 1011 | }; 1012 | 1013 | jasmine.JsApiReporter.prototype.suites = function() { 1014 | return this.suites_; 1015 | }; 1016 | 1017 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1018 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1019 | var summary = { 1020 | id: suiteOrSpec.id, 1021 | name: suiteOrSpec.description, 1022 | type: isSuite ? 'suite' : 'spec', 1023 | children: [] 1024 | }; 1025 | 1026 | if (isSuite) { 1027 | var children = suiteOrSpec.children(); 1028 | for (var i = 0; i < children.length; i++) { 1029 | summary.children.push(this.summarize_(children[i])); 1030 | } 1031 | } 1032 | return summary; 1033 | }; 1034 | 1035 | jasmine.JsApiReporter.prototype.results = function() { 1036 | return this.results_; 1037 | }; 1038 | 1039 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1040 | return this.results_[specId]; 1041 | }; 1042 | 1043 | //noinspection JSUnusedLocalSymbols 1044 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1045 | this.finished = true; 1046 | }; 1047 | 1048 | //noinspection JSUnusedLocalSymbols 1049 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1050 | }; 1051 | 1052 | //noinspection JSUnusedLocalSymbols 1053 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1054 | this.results_[spec.id] = { 1055 | messages: spec.results().getItems(), 1056 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1057 | }; 1058 | }; 1059 | 1060 | //noinspection JSUnusedLocalSymbols 1061 | jasmine.JsApiReporter.prototype.log = function(str) { 1062 | }; 1063 | 1064 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1065 | var results = {}; 1066 | for (var i = 0; i < specIds.length; i++) { 1067 | var specId = specIds[i]; 1068 | results[specId] = this.summarizeResult_(this.results_[specId]); 1069 | } 1070 | return results; 1071 | }; 1072 | 1073 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1074 | var summaryMessages = []; 1075 | var messagesLength = result.messages.length; 1076 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1077 | var resultMessage = result.messages[messageIndex]; 1078 | summaryMessages.push({ 1079 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1080 | passed: resultMessage.passed ? resultMessage.passed() : true, 1081 | type: resultMessage.type, 1082 | message: resultMessage.message, 1083 | trace: { 1084 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1085 | } 1086 | }); 1087 | } 1088 | 1089 | return { 1090 | result : result.result, 1091 | messages : summaryMessages 1092 | }; 1093 | }; 1094 | 1095 | /** 1096 | * @constructor 1097 | * @param {jasmine.Env} env 1098 | * @param actual 1099 | * @param {jasmine.Spec} spec 1100 | */ 1101 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1102 | this.env = env; 1103 | this.actual = actual; 1104 | this.spec = spec; 1105 | this.isNot = opt_isNot || false; 1106 | this.reportWasCalled_ = false; 1107 | }; 1108 | 1109 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1110 | jasmine.Matchers.pp = function(str) { 1111 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1112 | }; 1113 | 1114 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1115 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1116 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1117 | }; 1118 | 1119 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1120 | for (var methodName in prototype) { 1121 | if (methodName == 'report') continue; 1122 | var orig = prototype[methodName]; 1123 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1124 | } 1125 | }; 1126 | 1127 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1128 | return function() { 1129 | var matcherArgs = jasmine.util.argsToArray(arguments); 1130 | var result = matcherFunction.apply(this, arguments); 1131 | 1132 | if (this.isNot) { 1133 | result = !result; 1134 | } 1135 | 1136 | if (this.reportWasCalled_) return result; 1137 | 1138 | var message; 1139 | if (!result) { 1140 | if (this.message) { 1141 | message = this.message.apply(this, arguments); 1142 | if (jasmine.isArray_(message)) { 1143 | message = message[this.isNot ? 1 : 0]; 1144 | } 1145 | } else { 1146 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1147 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1148 | if (matcherArgs.length > 0) { 1149 | for (var i = 0; i < matcherArgs.length; i++) { 1150 | if (i > 0) message += ","; 1151 | message += " " + jasmine.pp(matcherArgs[i]); 1152 | } 1153 | } 1154 | message += "."; 1155 | } 1156 | } 1157 | var expectationResult = new jasmine.ExpectationResult({ 1158 | matcherName: matcherName, 1159 | passed: result, 1160 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1161 | actual: this.actual, 1162 | message: message 1163 | }); 1164 | this.spec.addMatcherResult(expectationResult); 1165 | return jasmine.undefined; 1166 | }; 1167 | }; 1168 | 1169 | 1170 | 1171 | 1172 | /** 1173 | * toBe: compares the actual to the expected using === 1174 | * @param expected 1175 | */ 1176 | jasmine.Matchers.prototype.toBe = function(expected) { 1177 | return this.actual === expected; 1178 | }; 1179 | 1180 | /** 1181 | * toNotBe: compares the actual to the expected using !== 1182 | * @param expected 1183 | * @deprecated as of 1.0. Use not.toBe() instead. 1184 | */ 1185 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1186 | return this.actual !== expected; 1187 | }; 1188 | 1189 | /** 1190 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1191 | * 1192 | * @param expected 1193 | */ 1194 | jasmine.Matchers.prototype.toEqual = function(expected) { 1195 | return this.env.equals_(this.actual, expected); 1196 | }; 1197 | 1198 | /** 1199 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1200 | * @param expected 1201 | * @deprecated as of 1.0. Use not.toNotEqual() instead. 1202 | */ 1203 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1204 | return !this.env.equals_(this.actual, expected); 1205 | }; 1206 | 1207 | /** 1208 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1209 | * a pattern or a String. 1210 | * 1211 | * @param expected 1212 | */ 1213 | jasmine.Matchers.prototype.toMatch = function(expected) { 1214 | return new RegExp(expected).test(this.actual); 1215 | }; 1216 | 1217 | /** 1218 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1219 | * @param expected 1220 | * @deprecated as of 1.0. Use not.toMatch() instead. 1221 | */ 1222 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1223 | return !(new RegExp(expected).test(this.actual)); 1224 | }; 1225 | 1226 | /** 1227 | * Matcher that compares the actual to jasmine.undefined. 1228 | */ 1229 | jasmine.Matchers.prototype.toBeDefined = function() { 1230 | return (this.actual !== jasmine.undefined); 1231 | }; 1232 | 1233 | /** 1234 | * Matcher that compares the actual to jasmine.undefined. 1235 | */ 1236 | jasmine.Matchers.prototype.toBeUndefined = function() { 1237 | return (this.actual === jasmine.undefined); 1238 | }; 1239 | 1240 | /** 1241 | * Matcher that compares the actual to null. 1242 | */ 1243 | jasmine.Matchers.prototype.toBeNull = function() { 1244 | return (this.actual === null); 1245 | }; 1246 | 1247 | /** 1248 | * Matcher that boolean not-nots the actual. 1249 | */ 1250 | jasmine.Matchers.prototype.toBeTruthy = function() { 1251 | return !!this.actual; 1252 | }; 1253 | 1254 | 1255 | /** 1256 | * Matcher that boolean nots the actual. 1257 | */ 1258 | jasmine.Matchers.prototype.toBeFalsy = function() { 1259 | return !this.actual; 1260 | }; 1261 | 1262 | 1263 | /** 1264 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1265 | */ 1266 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1267 | if (arguments.length > 0) { 1268 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1269 | } 1270 | 1271 | if (!jasmine.isSpy(this.actual)) { 1272 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1273 | } 1274 | 1275 | this.message = function() { 1276 | return [ 1277 | "Expected spy " + this.actual.identity + " to have been called.", 1278 | "Expected spy " + this.actual.identity + " not to have been called." 1279 | ]; 1280 | }; 1281 | 1282 | return this.actual.wasCalled; 1283 | }; 1284 | 1285 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1286 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1287 | 1288 | /** 1289 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1290 | * 1291 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1292 | */ 1293 | jasmine.Matchers.prototype.wasNotCalled = function() { 1294 | if (arguments.length > 0) { 1295 | throw new Error('wasNotCalled does not take arguments'); 1296 | } 1297 | 1298 | if (!jasmine.isSpy(this.actual)) { 1299 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1300 | } 1301 | 1302 | this.message = function() { 1303 | return [ 1304 | "Expected spy " + this.actual.identity + " to not have been called.", 1305 | "Expected spy " + this.actual.identity + " to have been called." 1306 | ]; 1307 | }; 1308 | 1309 | return !this.actual.wasCalled; 1310 | }; 1311 | 1312 | /** 1313 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1314 | * 1315 | * @example 1316 | * 1317 | */ 1318 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1319 | var expectedArgs = jasmine.util.argsToArray(arguments); 1320 | if (!jasmine.isSpy(this.actual)) { 1321 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1322 | } 1323 | this.message = function() { 1324 | if (this.actual.callCount === 0) { 1325 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1326 | return [ 1327 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1328 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1329 | ]; 1330 | } else { 1331 | return [ 1332 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1333 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1334 | ]; 1335 | } 1336 | }; 1337 | 1338 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1339 | }; 1340 | 1341 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1342 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1343 | 1344 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1345 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1346 | var expectedArgs = jasmine.util.argsToArray(arguments); 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 not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1354 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1355 | ]; 1356 | }; 1357 | 1358 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1359 | }; 1360 | 1361 | /** 1362 | * Matcher that checks that the expected item is an element in the actual Array. 1363 | * 1364 | * @param {Object} expected 1365 | */ 1366 | jasmine.Matchers.prototype.toContain = function(expected) { 1367 | return this.env.contains_(this.actual, expected); 1368 | }; 1369 | 1370 | /** 1371 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1372 | * 1373 | * @param {Object} expected 1374 | * @deprecated as of 1.0. Use not.toNotContain() instead. 1375 | */ 1376 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1377 | return !this.env.contains_(this.actual, expected); 1378 | }; 1379 | 1380 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1381 | return this.actual < expected; 1382 | }; 1383 | 1384 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1385 | return this.actual > expected; 1386 | }; 1387 | 1388 | /** 1389 | * Matcher that checks that the expected exception was thrown by the actual. 1390 | * 1391 | * @param {String} expected 1392 | */ 1393 | jasmine.Matchers.prototype.toThrow = function(expected) { 1394 | var result = false; 1395 | var exception; 1396 | if (typeof this.actual != 'function') { 1397 | throw new Error('Actual is not a function'); 1398 | } 1399 | try { 1400 | this.actual(); 1401 | } catch (e) { 1402 | exception = e; 1403 | } 1404 | if (exception) { 1405 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1406 | } 1407 | 1408 | var not = this.isNot ? "not " : ""; 1409 | 1410 | this.message = function() { 1411 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1412 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1413 | } else { 1414 | return "Expected function to throw an exception."; 1415 | } 1416 | }; 1417 | 1418 | return result; 1419 | }; 1420 | 1421 | jasmine.Matchers.Any = function(expectedClass) { 1422 | this.expectedClass = expectedClass; 1423 | }; 1424 | 1425 | jasmine.Matchers.Any.prototype.matches = function(other) { 1426 | if (this.expectedClass == String) { 1427 | return typeof other == 'string' || other instanceof String; 1428 | } 1429 | 1430 | if (this.expectedClass == Number) { 1431 | return typeof other == 'number' || other instanceof Number; 1432 | } 1433 | 1434 | if (this.expectedClass == Function) { 1435 | return typeof other == 'function' || other instanceof Function; 1436 | } 1437 | 1438 | if (this.expectedClass == Object) { 1439 | return typeof other == 'object'; 1440 | } 1441 | 1442 | return other instanceof this.expectedClass; 1443 | }; 1444 | 1445 | jasmine.Matchers.Any.prototype.toString = function() { 1446 | return ''; 1447 | }; 1448 | 1449 | /** 1450 | * @constructor 1451 | */ 1452 | jasmine.MultiReporter = function() { 1453 | this.subReporters_ = []; 1454 | }; 1455 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1456 | 1457 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1458 | this.subReporters_.push(reporter); 1459 | }; 1460 | 1461 | (function() { 1462 | var functionNames = [ 1463 | "reportRunnerStarting", 1464 | "reportRunnerResults", 1465 | "reportSuiteResults", 1466 | "reportSpecStarting", 1467 | "reportSpecResults", 1468 | "log" 1469 | ]; 1470 | for (var i = 0; i < functionNames.length; i++) { 1471 | var functionName = functionNames[i]; 1472 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1473 | return function() { 1474 | for (var j = 0; j < this.subReporters_.length; j++) { 1475 | var subReporter = this.subReporters_[j]; 1476 | if (subReporter[functionName]) { 1477 | subReporter[functionName].apply(subReporter, arguments); 1478 | } 1479 | } 1480 | }; 1481 | })(functionName); 1482 | } 1483 | })(); 1484 | /** 1485 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1486 | * 1487 | * @constructor 1488 | */ 1489 | jasmine.NestedResults = function() { 1490 | /** 1491 | * The total count of results 1492 | */ 1493 | this.totalCount = 0; 1494 | /** 1495 | * Number of passed results 1496 | */ 1497 | this.passedCount = 0; 1498 | /** 1499 | * Number of failed results 1500 | */ 1501 | this.failedCount = 0; 1502 | /** 1503 | * Was this suite/spec skipped? 1504 | */ 1505 | this.skipped = false; 1506 | /** 1507 | * @ignore 1508 | */ 1509 | this.items_ = []; 1510 | }; 1511 | 1512 | /** 1513 | * Roll up the result counts. 1514 | * 1515 | * @param result 1516 | */ 1517 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1518 | this.totalCount += result.totalCount; 1519 | this.passedCount += result.passedCount; 1520 | this.failedCount += result.failedCount; 1521 | }; 1522 | 1523 | /** 1524 | * Adds a log message. 1525 | * @param values Array of message parts which will be concatenated later. 1526 | */ 1527 | jasmine.NestedResults.prototype.log = function(values) { 1528 | this.items_.push(new jasmine.MessageResult(values)); 1529 | }; 1530 | 1531 | /** 1532 | * Getter for the results: message & results. 1533 | */ 1534 | jasmine.NestedResults.prototype.getItems = function() { 1535 | return this.items_; 1536 | }; 1537 | 1538 | /** 1539 | * Adds a result, tracking counts (total, passed, & failed) 1540 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1541 | */ 1542 | jasmine.NestedResults.prototype.addResult = function(result) { 1543 | if (result.type != 'log') { 1544 | if (result.items_) { 1545 | this.rollupCounts(result); 1546 | } else { 1547 | this.totalCount++; 1548 | if (result.passed()) { 1549 | this.passedCount++; 1550 | } else { 1551 | this.failedCount++; 1552 | } 1553 | } 1554 | } 1555 | this.items_.push(result); 1556 | }; 1557 | 1558 | /** 1559 | * @returns {Boolean} True if everything below passed 1560 | */ 1561 | jasmine.NestedResults.prototype.passed = function() { 1562 | return this.passedCount === this.totalCount; 1563 | }; 1564 | /** 1565 | * Base class for pretty printing for expectation results. 1566 | */ 1567 | jasmine.PrettyPrinter = function() { 1568 | this.ppNestLevel_ = 0; 1569 | }; 1570 | 1571 | /** 1572 | * Formats a value in a nice, human-readable string. 1573 | * 1574 | * @param value 1575 | */ 1576 | jasmine.PrettyPrinter.prototype.format = function(value) { 1577 | if (this.ppNestLevel_ > 40) { 1578 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1579 | } 1580 | 1581 | this.ppNestLevel_++; 1582 | try { 1583 | if (value === jasmine.undefined) { 1584 | this.emitScalar('undefined'); 1585 | } else if (value === null) { 1586 | this.emitScalar('null'); 1587 | } else if (value === jasmine.getGlobal()) { 1588 | this.emitScalar(''); 1589 | } else if (value instanceof jasmine.Matchers.Any) { 1590 | this.emitScalar(value.toString()); 1591 | } else if (typeof value === 'string') { 1592 | this.emitString(value); 1593 | } else if (jasmine.isSpy(value)) { 1594 | this.emitScalar("spy on " + value.identity); 1595 | } else if (value instanceof RegExp) { 1596 | this.emitScalar(value.toString()); 1597 | } else if (typeof value === 'function') { 1598 | this.emitScalar('Function'); 1599 | } else if (typeof value.nodeType === 'number') { 1600 | this.emitScalar('HTMLNode'); 1601 | } else if (value instanceof Date) { 1602 | this.emitScalar('Date(' + value + ')'); 1603 | } else if (value.__Jasmine_been_here_before__) { 1604 | this.emitScalar(''); 1605 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1606 | value.__Jasmine_been_here_before__ = true; 1607 | if (jasmine.isArray_(value)) { 1608 | this.emitArray(value); 1609 | } else { 1610 | this.emitObject(value); 1611 | } 1612 | delete value.__Jasmine_been_here_before__; 1613 | } else { 1614 | this.emitScalar(value.toString()); 1615 | } 1616 | } finally { 1617 | this.ppNestLevel_--; 1618 | } 1619 | }; 1620 | 1621 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1622 | for (var property in obj) { 1623 | if (property == '__Jasmine_been_here_before__') continue; 1624 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1625 | obj.__lookupGetter__(property) !== null) : false); 1626 | } 1627 | }; 1628 | 1629 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1630 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1631 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1632 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1633 | 1634 | jasmine.StringPrettyPrinter = function() { 1635 | jasmine.PrettyPrinter.call(this); 1636 | 1637 | this.string = ''; 1638 | }; 1639 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1640 | 1641 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1642 | this.append(value); 1643 | }; 1644 | 1645 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1646 | this.append("'" + value + "'"); 1647 | }; 1648 | 1649 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1650 | this.append('[ '); 1651 | for (var i = 0; i < array.length; i++) { 1652 | if (i > 0) { 1653 | this.append(', '); 1654 | } 1655 | this.format(array[i]); 1656 | } 1657 | this.append(' ]'); 1658 | }; 1659 | 1660 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1661 | var self = this; 1662 | this.append('{ '); 1663 | var first = true; 1664 | 1665 | this.iterateObject(obj, function(property, isGetter) { 1666 | if (first) { 1667 | first = false; 1668 | } else { 1669 | self.append(', '); 1670 | } 1671 | 1672 | self.append(property); 1673 | self.append(' : '); 1674 | if (isGetter) { 1675 | self.append(''); 1676 | } else { 1677 | self.format(obj[property]); 1678 | } 1679 | }); 1680 | 1681 | this.append(' }'); 1682 | }; 1683 | 1684 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1685 | this.string += value; 1686 | }; 1687 | jasmine.Queue = function(env) { 1688 | this.env = env; 1689 | this.blocks = []; 1690 | this.running = false; 1691 | this.index = 0; 1692 | this.offset = 0; 1693 | this.abort = false; 1694 | }; 1695 | 1696 | jasmine.Queue.prototype.addBefore = function(block) { 1697 | this.blocks.unshift(block); 1698 | }; 1699 | 1700 | jasmine.Queue.prototype.add = function(block) { 1701 | this.blocks.push(block); 1702 | }; 1703 | 1704 | jasmine.Queue.prototype.insertNext = function(block) { 1705 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1706 | this.offset++; 1707 | }; 1708 | 1709 | jasmine.Queue.prototype.start = function(onComplete) { 1710 | this.running = true; 1711 | this.onComplete = onComplete; 1712 | this.next_(); 1713 | }; 1714 | 1715 | jasmine.Queue.prototype.isRunning = function() { 1716 | return this.running; 1717 | }; 1718 | 1719 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1720 | 1721 | jasmine.Queue.prototype.next_ = function() { 1722 | var self = this; 1723 | var goAgain = true; 1724 | 1725 | while (goAgain) { 1726 | goAgain = false; 1727 | 1728 | if (self.index < self.blocks.length && !this.abort) { 1729 | var calledSynchronously = true; 1730 | var completedSynchronously = false; 1731 | 1732 | var onComplete = function () { 1733 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1734 | completedSynchronously = true; 1735 | return; 1736 | } 1737 | 1738 | if (self.blocks[self.index].abort) { 1739 | self.abort = true; 1740 | } 1741 | 1742 | self.offset = 0; 1743 | self.index++; 1744 | 1745 | var now = new Date().getTime(); 1746 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1747 | self.env.lastUpdate = now; 1748 | self.env.setTimeout(function() { 1749 | self.next_(); 1750 | }, 0); 1751 | } else { 1752 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1753 | goAgain = true; 1754 | } else { 1755 | self.next_(); 1756 | } 1757 | } 1758 | }; 1759 | self.blocks[self.index].execute(onComplete); 1760 | 1761 | calledSynchronously = false; 1762 | if (completedSynchronously) { 1763 | onComplete(); 1764 | } 1765 | 1766 | } else { 1767 | self.running = false; 1768 | if (self.onComplete) { 1769 | self.onComplete(); 1770 | } 1771 | } 1772 | } 1773 | }; 1774 | 1775 | jasmine.Queue.prototype.results = function() { 1776 | var results = new jasmine.NestedResults(); 1777 | for (var i = 0; i < this.blocks.length; i++) { 1778 | if (this.blocks[i].results) { 1779 | results.addResult(this.blocks[i].results()); 1780 | } 1781 | } 1782 | return results; 1783 | }; 1784 | 1785 | 1786 | /** 1787 | * Runner 1788 | * 1789 | * @constructor 1790 | * @param {jasmine.Env} env 1791 | */ 1792 | jasmine.Runner = function(env) { 1793 | var self = this; 1794 | self.env = env; 1795 | self.queue = new jasmine.Queue(env); 1796 | self.before_ = []; 1797 | self.after_ = []; 1798 | self.suites_ = []; 1799 | }; 1800 | 1801 | jasmine.Runner.prototype.execute = function() { 1802 | var self = this; 1803 | if (self.env.reporter.reportRunnerStarting) { 1804 | self.env.reporter.reportRunnerStarting(this); 1805 | } 1806 | self.queue.start(function () { 1807 | self.finishCallback(); 1808 | }); 1809 | }; 1810 | 1811 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1812 | beforeEachFunction.typeName = 'beforeEach'; 1813 | this.before_.splice(0,0,beforeEachFunction); 1814 | }; 1815 | 1816 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1817 | afterEachFunction.typeName = 'afterEach'; 1818 | this.after_.splice(0,0,afterEachFunction); 1819 | }; 1820 | 1821 | 1822 | jasmine.Runner.prototype.finishCallback = function() { 1823 | this.env.reporter.reportRunnerResults(this); 1824 | }; 1825 | 1826 | jasmine.Runner.prototype.addSuite = function(suite) { 1827 | this.suites_.push(suite); 1828 | }; 1829 | 1830 | jasmine.Runner.prototype.add = function(block) { 1831 | if (block instanceof jasmine.Suite) { 1832 | this.addSuite(block); 1833 | } 1834 | this.queue.add(block); 1835 | }; 1836 | 1837 | jasmine.Runner.prototype.specs = function () { 1838 | var suites = this.suites(); 1839 | var specs = []; 1840 | for (var i = 0; i < suites.length; i++) { 1841 | specs = specs.concat(suites[i].specs()); 1842 | } 1843 | return specs; 1844 | }; 1845 | 1846 | jasmine.Runner.prototype.suites = function() { 1847 | return this.suites_; 1848 | }; 1849 | 1850 | jasmine.Runner.prototype.topLevelSuites = function() { 1851 | var topLevelSuites = []; 1852 | for (var i = 0; i < this.suites_.length; i++) { 1853 | if (!this.suites_[i].parentSuite) { 1854 | topLevelSuites.push(this.suites_[i]); 1855 | } 1856 | } 1857 | return topLevelSuites; 1858 | }; 1859 | 1860 | jasmine.Runner.prototype.results = function() { 1861 | return this.queue.results(); 1862 | }; 1863 | /** 1864 | * Internal representation of a Jasmine specification, or test. 1865 | * 1866 | * @constructor 1867 | * @param {jasmine.Env} env 1868 | * @param {jasmine.Suite} suite 1869 | * @param {String} description 1870 | */ 1871 | jasmine.Spec = function(env, suite, description) { 1872 | if (!env) { 1873 | throw new Error('jasmine.Env() required'); 1874 | } 1875 | if (!suite) { 1876 | throw new Error('jasmine.Suite() required'); 1877 | } 1878 | var spec = this; 1879 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 1880 | spec.env = env; 1881 | spec.suite = suite; 1882 | spec.description = description; 1883 | spec.queue = new jasmine.Queue(env); 1884 | 1885 | spec.afterCallbacks = []; 1886 | spec.spies_ = []; 1887 | 1888 | spec.results_ = new jasmine.NestedResults(); 1889 | spec.results_.description = description; 1890 | spec.matchersClass = null; 1891 | }; 1892 | 1893 | jasmine.Spec.prototype.getFullName = function() { 1894 | return this.suite.getFullName() + ' ' + this.description + '.'; 1895 | }; 1896 | 1897 | 1898 | jasmine.Spec.prototype.results = function() { 1899 | return this.results_; 1900 | }; 1901 | 1902 | /** 1903 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 1904 | * 1905 | * Be careful not to leave calls to jasmine.log in production code. 1906 | */ 1907 | jasmine.Spec.prototype.log = function() { 1908 | return this.results_.log(arguments); 1909 | }; 1910 | 1911 | jasmine.Spec.prototype.runs = function (func) { 1912 | var block = new jasmine.Block(this.env, func, this); 1913 | this.addToQueue(block); 1914 | return this; 1915 | }; 1916 | 1917 | jasmine.Spec.prototype.addToQueue = function (block) { 1918 | if (this.queue.isRunning()) { 1919 | this.queue.insertNext(block); 1920 | } else { 1921 | this.queue.add(block); 1922 | } 1923 | }; 1924 | 1925 | /** 1926 | * @param {jasmine.ExpectationResult} result 1927 | */ 1928 | jasmine.Spec.prototype.addMatcherResult = function(result) { 1929 | this.results_.addResult(result); 1930 | }; 1931 | 1932 | jasmine.Spec.prototype.expect = function(actual) { 1933 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 1934 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 1935 | return positive; 1936 | }; 1937 | 1938 | /** 1939 | * Waits a fixed time period before moving to the next block. 1940 | * 1941 | * @deprecated Use waitsFor() instead 1942 | * @param {Number} timeout milliseconds to wait 1943 | */ 1944 | jasmine.Spec.prototype.waits = function(timeout) { 1945 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1946 | this.addToQueue(waitsFunc); 1947 | return this; 1948 | }; 1949 | 1950 | /** 1951 | * Waits for the latchFunction to return true before proceeding to the next block. 1952 | * 1953 | * @param {Function} latchFunction 1954 | * @param {String} optional_timeoutMessage 1955 | * @param {Number} optional_timeout 1956 | */ 1957 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 1958 | var latchFunction_ = null; 1959 | var optional_timeoutMessage_ = null; 1960 | var optional_timeout_ = null; 1961 | 1962 | for (var i = 0; i < arguments.length; i++) { 1963 | var arg = arguments[i]; 1964 | switch (typeof arg) { 1965 | case 'function': 1966 | latchFunction_ = arg; 1967 | break; 1968 | case 'string': 1969 | optional_timeoutMessage_ = arg; 1970 | break; 1971 | case 'number': 1972 | optional_timeout_ = arg; 1973 | break; 1974 | } 1975 | } 1976 | 1977 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 1978 | this.addToQueue(waitsForFunc); 1979 | return this; 1980 | }; 1981 | 1982 | jasmine.Spec.prototype.fail = function (e) { 1983 | var expectationResult = new jasmine.ExpectationResult({ 1984 | passed: false, 1985 | message: e ? jasmine.util.formatException(e) : 'Exception' 1986 | }); 1987 | this.results_.addResult(expectationResult); 1988 | }; 1989 | 1990 | jasmine.Spec.prototype.getMatchersClass_ = function() { 1991 | return this.matchersClass || this.env.matchersClass; 1992 | }; 1993 | 1994 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 1995 | var parent = this.getMatchersClass_(); 1996 | var newMatchersClass = function() { 1997 | parent.apply(this, arguments); 1998 | }; 1999 | jasmine.util.inherit(newMatchersClass, parent); 2000 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2001 | this.matchersClass = newMatchersClass; 2002 | }; 2003 | 2004 | jasmine.Spec.prototype.finishCallback = function() { 2005 | this.env.reporter.reportSpecResults(this); 2006 | }; 2007 | 2008 | jasmine.Spec.prototype.finish = function(onComplete) { 2009 | this.removeAllSpies(); 2010 | this.finishCallback(); 2011 | if (onComplete) { 2012 | onComplete(); 2013 | } 2014 | }; 2015 | 2016 | jasmine.Spec.prototype.after = function(doAfter) { 2017 | if (this.queue.isRunning()) { 2018 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2019 | } else { 2020 | this.afterCallbacks.unshift(doAfter); 2021 | } 2022 | }; 2023 | 2024 | jasmine.Spec.prototype.execute = function(onComplete) { 2025 | var spec = this; 2026 | if (!spec.env.specFilter(spec)) { 2027 | spec.results_.skipped = true; 2028 | spec.finish(onComplete); 2029 | return; 2030 | } 2031 | 2032 | this.env.reporter.reportSpecStarting(this); 2033 | 2034 | spec.env.currentSpec = spec; 2035 | 2036 | spec.addBeforesAndAftersToQueue(); 2037 | 2038 | spec.queue.start(function () { 2039 | spec.finish(onComplete); 2040 | }); 2041 | }; 2042 | 2043 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2044 | var runner = this.env.currentRunner(); 2045 | var i; 2046 | 2047 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2048 | for (i = 0; i < suite.before_.length; i++) { 2049 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2050 | } 2051 | } 2052 | for (i = 0; i < runner.before_.length; i++) { 2053 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2054 | } 2055 | for (i = 0; i < this.afterCallbacks.length; i++) { 2056 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2057 | } 2058 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2059 | for (i = 0; i < suite.after_.length; i++) { 2060 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2061 | } 2062 | } 2063 | for (i = 0; i < runner.after_.length; i++) { 2064 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2065 | } 2066 | }; 2067 | 2068 | jasmine.Spec.prototype.explodes = function() { 2069 | throw 'explodes function should not have been called'; 2070 | }; 2071 | 2072 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2073 | if (obj == jasmine.undefined) { 2074 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2075 | } 2076 | 2077 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2078 | throw methodName + '() method does not exist'; 2079 | } 2080 | 2081 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2082 | throw new Error(methodName + ' has already been spied upon'); 2083 | } 2084 | 2085 | var spyObj = jasmine.createSpy(methodName); 2086 | 2087 | this.spies_.push(spyObj); 2088 | spyObj.baseObj = obj; 2089 | spyObj.methodName = methodName; 2090 | spyObj.originalValue = obj[methodName]; 2091 | 2092 | obj[methodName] = spyObj; 2093 | 2094 | return spyObj; 2095 | }; 2096 | 2097 | jasmine.Spec.prototype.removeAllSpies = function() { 2098 | for (var i = 0; i < this.spies_.length; i++) { 2099 | var spy = this.spies_[i]; 2100 | spy.baseObj[spy.methodName] = spy.originalValue; 2101 | } 2102 | this.spies_ = []; 2103 | }; 2104 | 2105 | /** 2106 | * Internal representation of a Jasmine suite. 2107 | * 2108 | * @constructor 2109 | * @param {jasmine.Env} env 2110 | * @param {String} description 2111 | * @param {Function} specDefinitions 2112 | * @param {jasmine.Suite} parentSuite 2113 | */ 2114 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2115 | var self = this; 2116 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2117 | self.description = description; 2118 | self.queue = new jasmine.Queue(env); 2119 | self.parentSuite = parentSuite; 2120 | self.env = env; 2121 | self.before_ = []; 2122 | self.after_ = []; 2123 | self.children_ = []; 2124 | self.suites_ = []; 2125 | self.specs_ = []; 2126 | }; 2127 | 2128 | jasmine.Suite.prototype.getFullName = function() { 2129 | var fullName = this.description; 2130 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2131 | fullName = parentSuite.description + ' ' + fullName; 2132 | } 2133 | return fullName; 2134 | }; 2135 | 2136 | jasmine.Suite.prototype.finish = function(onComplete) { 2137 | this.env.reporter.reportSuiteResults(this); 2138 | this.finished = true; 2139 | if (typeof(onComplete) == 'function') { 2140 | onComplete(); 2141 | } 2142 | }; 2143 | 2144 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2145 | beforeEachFunction.typeName = 'beforeEach'; 2146 | this.before_.unshift(beforeEachFunction); 2147 | }; 2148 | 2149 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2150 | afterEachFunction.typeName = 'afterEach'; 2151 | this.after_.unshift(afterEachFunction); 2152 | }; 2153 | 2154 | jasmine.Suite.prototype.results = function() { 2155 | return this.queue.results(); 2156 | }; 2157 | 2158 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2159 | this.children_.push(suiteOrSpec); 2160 | if (suiteOrSpec instanceof jasmine.Suite) { 2161 | this.suites_.push(suiteOrSpec); 2162 | this.env.currentRunner().addSuite(suiteOrSpec); 2163 | } else { 2164 | this.specs_.push(suiteOrSpec); 2165 | } 2166 | this.queue.add(suiteOrSpec); 2167 | }; 2168 | 2169 | jasmine.Suite.prototype.specs = function() { 2170 | return this.specs_; 2171 | }; 2172 | 2173 | jasmine.Suite.prototype.suites = function() { 2174 | return this.suites_; 2175 | }; 2176 | 2177 | jasmine.Suite.prototype.children = function() { 2178 | return this.children_; 2179 | }; 2180 | 2181 | jasmine.Suite.prototype.execute = function(onComplete) { 2182 | var self = this; 2183 | this.queue.start(function () { 2184 | self.finish(onComplete); 2185 | }); 2186 | }; 2187 | jasmine.WaitsBlock = function(env, timeout, spec) { 2188 | this.timeout = timeout; 2189 | jasmine.Block.call(this, env, null, spec); 2190 | }; 2191 | 2192 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2193 | 2194 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2195 | if (jasmine.VERBOSE) { 2196 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2197 | } 2198 | this.env.setTimeout(function () { 2199 | onComplete(); 2200 | }, this.timeout); 2201 | }; 2202 | /** 2203 | * A block which waits for some condition to become true, with timeout. 2204 | * 2205 | * @constructor 2206 | * @extends jasmine.Block 2207 | * @param {jasmine.Env} env The Jasmine environment. 2208 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2209 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2210 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2211 | * @param {jasmine.Spec} spec The Jasmine spec. 2212 | */ 2213 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2214 | this.timeout = timeout || env.defaultTimeoutInterval; 2215 | this.latchFunction = latchFunction; 2216 | this.message = message; 2217 | this.totalTimeSpentWaitingForLatch = 0; 2218 | jasmine.Block.call(this, env, null, spec); 2219 | }; 2220 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2221 | 2222 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2223 | 2224 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2225 | if (jasmine.VERBOSE) { 2226 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2227 | } 2228 | var latchFunctionResult; 2229 | try { 2230 | latchFunctionResult = this.latchFunction.apply(this.spec); 2231 | } catch (e) { 2232 | this.spec.fail(e); 2233 | onComplete(); 2234 | return; 2235 | } 2236 | 2237 | if (latchFunctionResult) { 2238 | onComplete(); 2239 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2240 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2241 | this.spec.fail({ 2242 | name: 'timeout', 2243 | message: message 2244 | }); 2245 | 2246 | this.abort = true; 2247 | onComplete(); 2248 | } else { 2249 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2250 | var self = this; 2251 | this.env.setTimeout(function() { 2252 | self.execute(onComplete); 2253 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2254 | } 2255 | }; 2256 | // Mock setTimeout, clearTimeout 2257 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 2258 | 2259 | jasmine.FakeTimer = function() { 2260 | this.reset(); 2261 | 2262 | var self = this; 2263 | self.setTimeout = function(funcToCall, millis) { 2264 | self.timeoutsMade++; 2265 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 2266 | return self.timeoutsMade; 2267 | }; 2268 | 2269 | self.setInterval = function(funcToCall, millis) { 2270 | self.timeoutsMade++; 2271 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 2272 | return self.timeoutsMade; 2273 | }; 2274 | 2275 | self.clearTimeout = function(timeoutKey) { 2276 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2277 | }; 2278 | 2279 | self.clearInterval = function(timeoutKey) { 2280 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2281 | }; 2282 | 2283 | }; 2284 | 2285 | jasmine.FakeTimer.prototype.reset = function() { 2286 | this.timeoutsMade = 0; 2287 | this.scheduledFunctions = {}; 2288 | this.nowMillis = 0; 2289 | }; 2290 | 2291 | jasmine.FakeTimer.prototype.tick = function(millis) { 2292 | var oldMillis = this.nowMillis; 2293 | var newMillis = oldMillis + millis; 2294 | this.runFunctionsWithinRange(oldMillis, newMillis); 2295 | this.nowMillis = newMillis; 2296 | }; 2297 | 2298 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2299 | var scheduledFunc; 2300 | var funcsToRun = []; 2301 | for (var timeoutKey in this.scheduledFunctions) { 2302 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 2303 | if (scheduledFunc != jasmine.undefined && 2304 | scheduledFunc.runAtMillis >= oldMillis && 2305 | scheduledFunc.runAtMillis <= nowMillis) { 2306 | funcsToRun.push(scheduledFunc); 2307 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 2308 | } 2309 | } 2310 | 2311 | if (funcsToRun.length > 0) { 2312 | funcsToRun.sort(function(a, b) { 2313 | return a.runAtMillis - b.runAtMillis; 2314 | }); 2315 | for (var i = 0; i < funcsToRun.length; ++i) { 2316 | try { 2317 | var funcToRun = funcsToRun[i]; 2318 | this.nowMillis = funcToRun.runAtMillis; 2319 | funcToRun.funcToCall(); 2320 | if (funcToRun.recurring) { 2321 | this.scheduleFunction(funcToRun.timeoutKey, 2322 | funcToRun.funcToCall, 2323 | funcToRun.millis, 2324 | true); 2325 | } 2326 | } catch(e) { 2327 | } 2328 | } 2329 | this.runFunctionsWithinRange(oldMillis, nowMillis); 2330 | } 2331 | }; 2332 | 2333 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2334 | this.scheduledFunctions[timeoutKey] = { 2335 | runAtMillis: this.nowMillis + millis, 2336 | funcToCall: funcToCall, 2337 | recurring: recurring, 2338 | timeoutKey: timeoutKey, 2339 | millis: millis 2340 | }; 2341 | }; 2342 | 2343 | /** 2344 | * @namespace 2345 | */ 2346 | jasmine.Clock = { 2347 | defaultFakeTimer: new jasmine.FakeTimer(), 2348 | 2349 | reset: function() { 2350 | jasmine.Clock.assertInstalled(); 2351 | jasmine.Clock.defaultFakeTimer.reset(); 2352 | }, 2353 | 2354 | tick: function(millis) { 2355 | jasmine.Clock.assertInstalled(); 2356 | jasmine.Clock.defaultFakeTimer.tick(millis); 2357 | }, 2358 | 2359 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 2360 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2361 | }, 2362 | 2363 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2364 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2365 | }, 2366 | 2367 | useMock: function() { 2368 | if (!jasmine.Clock.isInstalled()) { 2369 | var spec = jasmine.getEnv().currentSpec; 2370 | spec.after(jasmine.Clock.uninstallMock); 2371 | 2372 | jasmine.Clock.installMock(); 2373 | } 2374 | }, 2375 | 2376 | installMock: function() { 2377 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2378 | }, 2379 | 2380 | uninstallMock: function() { 2381 | jasmine.Clock.assertInstalled(); 2382 | jasmine.Clock.installed = jasmine.Clock.real; 2383 | }, 2384 | 2385 | real: { 2386 | setTimeout: jasmine.getGlobal().setTimeout, 2387 | clearTimeout: jasmine.getGlobal().clearTimeout, 2388 | setInterval: jasmine.getGlobal().setInterval, 2389 | clearInterval: jasmine.getGlobal().clearInterval 2390 | }, 2391 | 2392 | assertInstalled: function() { 2393 | if (!jasmine.Clock.isInstalled()) { 2394 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2395 | } 2396 | }, 2397 | 2398 | isInstalled: function() { 2399 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 2400 | }, 2401 | 2402 | installed: null 2403 | }; 2404 | jasmine.Clock.installed = jasmine.Clock.real; 2405 | 2406 | //else for IE support 2407 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 2408 | if (jasmine.Clock.installed.setTimeout.apply) { 2409 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2410 | } else { 2411 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2412 | } 2413 | }; 2414 | 2415 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 2416 | if (jasmine.Clock.installed.setInterval.apply) { 2417 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 2418 | } else { 2419 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 2420 | } 2421 | }; 2422 | 2423 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 2424 | if (jasmine.Clock.installed.clearTimeout.apply) { 2425 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2426 | } else { 2427 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 2428 | } 2429 | }; 2430 | 2431 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 2432 | if (jasmine.Clock.installed.clearTimeout.apply) { 2433 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2434 | } else { 2435 | return jasmine.Clock.installed.clearInterval(timeoutKey); 2436 | } 2437 | }; 2438 | 2439 | 2440 | jasmine.version_= { 2441 | "major": 1, 2442 | "minor": 1, 2443 | "build": 0, 2444 | "revision": 1304737707 2445 | }; 2446 | -------------------------------------------------------------------------------- /tests/assets/jquery.ui.core.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI 1.8.12 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI 9 | */ 10 | (function( $, undefined ) { 11 | 12 | // prevent duplicate loading 13 | // this is only a problem because we proxy existing functions 14 | // and we don't want to double proxy them 15 | $.ui = $.ui || {}; 16 | if ( $.ui.version ) { 17 | return; 18 | } 19 | 20 | $.extend( $.ui, { 21 | version: "1.8.12", 22 | 23 | keyCode: { 24 | ALT: 18, 25 | BACKSPACE: 8, 26 | CAPS_LOCK: 20, 27 | COMMA: 188, 28 | COMMAND: 91, 29 | COMMAND_LEFT: 91, // COMMAND 30 | COMMAND_RIGHT: 93, 31 | CONTROL: 17, 32 | DELETE: 46, 33 | DOWN: 40, 34 | END: 35, 35 | ENTER: 13, 36 | ESCAPE: 27, 37 | HOME: 36, 38 | INSERT: 45, 39 | LEFT: 37, 40 | MENU: 93, // COMMAND_RIGHT 41 | NUMPAD_ADD: 107, 42 | NUMPAD_DECIMAL: 110, 43 | NUMPAD_DIVIDE: 111, 44 | NUMPAD_ENTER: 108, 45 | NUMPAD_MULTIPLY: 106, 46 | NUMPAD_SUBTRACT: 109, 47 | PAGE_DOWN: 34, 48 | PAGE_UP: 33, 49 | PERIOD: 190, 50 | RIGHT: 39, 51 | SHIFT: 16, 52 | SPACE: 32, 53 | TAB: 9, 54 | UP: 38, 55 | WINDOWS: 91 // COMMAND 56 | } 57 | }); 58 | 59 | // plugins 60 | $.fn.extend({ 61 | _focus: $.fn.focus, 62 | focus: function( delay, fn ) { 63 | return typeof delay === "number" ? 64 | this.each(function() { 65 | var elem = this; 66 | setTimeout(function() { 67 | $( elem ).focus(); 68 | if ( fn ) { 69 | fn.call( elem ); 70 | } 71 | }, delay ); 72 | }) : 73 | this._focus.apply( this, arguments ); 74 | }, 75 | 76 | scrollParent: function() { 77 | var scrollParent; 78 | if (($.browser.msie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) { 79 | scrollParent = this.parents().filter(function() { 80 | return (/(relative|absolute|fixed)/).test($.curCSS(this,'position',1)) && (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); 81 | }).eq(0); 82 | } else { 83 | scrollParent = this.parents().filter(function() { 84 | return (/(auto|scroll)/).test($.curCSS(this,'overflow',1)+$.curCSS(this,'overflow-y',1)+$.curCSS(this,'overflow-x',1)); 85 | }).eq(0); 86 | } 87 | 88 | return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent; 89 | }, 90 | 91 | zIndex: function( zIndex ) { 92 | if ( zIndex !== undefined ) { 93 | return this.css( "zIndex", zIndex ); 94 | } 95 | 96 | if ( this.length ) { 97 | var elem = $( this[ 0 ] ), position, value; 98 | while ( elem.length && elem[ 0 ] !== document ) { 99 | // Ignore z-index if position is set to a value where z-index is ignored by the browser 100 | // This makes behavior of this function consistent across browsers 101 | // WebKit always returns auto if the element is positioned 102 | position = elem.css( "position" ); 103 | if ( position === "absolute" || position === "relative" || position === "fixed" ) { 104 | // IE returns 0 when zIndex is not specified 105 | // other browsers return a string 106 | // we ignore the case of nested elements with an explicit value of 0 107 | //
108 | value = parseInt( elem.css( "zIndex" ), 10 ); 109 | if ( !isNaN( value ) && value !== 0 ) { 110 | return value; 111 | } 112 | } 113 | elem = elem.parent(); 114 | } 115 | } 116 | 117 | return 0; 118 | }, 119 | 120 | disableSelection: function() { 121 | return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + 122 | ".ui-disableSelection", function( event ) { 123 | event.preventDefault(); 124 | }); 125 | }, 126 | 127 | enableSelection: function() { 128 | return this.unbind( ".ui-disableSelection" ); 129 | } 130 | }); 131 | 132 | $.each( [ "Width", "Height" ], function( i, name ) { 133 | var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], 134 | type = name.toLowerCase(), 135 | orig = { 136 | innerWidth: $.fn.innerWidth, 137 | innerHeight: $.fn.innerHeight, 138 | outerWidth: $.fn.outerWidth, 139 | outerHeight: $.fn.outerHeight 140 | }; 141 | 142 | function reduce( elem, size, border, margin ) { 143 | $.each( side, function() { 144 | size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; 145 | if ( border ) { 146 | size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; 147 | } 148 | if ( margin ) { 149 | size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; 150 | } 151 | }); 152 | return size; 153 | } 154 | 155 | $.fn[ "inner" + name ] = function( size ) { 156 | if ( size === undefined ) { 157 | return orig[ "inner" + name ].call( this ); 158 | } 159 | 160 | return this.each(function() { 161 | $( this ).css( type, reduce( this, size ) + "px" ); 162 | }); 163 | }; 164 | 165 | $.fn[ "outer" + name] = function( size, margin ) { 166 | if ( typeof size !== "number" ) { 167 | return orig[ "outer" + name ].call( this, size ); 168 | } 169 | 170 | return this.each(function() { 171 | $( this).css( type, reduce( this, size, true, margin ) + "px" ); 172 | }); 173 | }; 174 | }); 175 | 176 | // selectors 177 | function visible( element ) { 178 | return !$( element ).parents().andSelf().filter(function() { 179 | return $.curCSS( this, "visibility" ) === "hidden" || 180 | $.expr.filters.hidden( this ); 181 | }).length; 182 | } 183 | 184 | $.extend( $.expr[ ":" ], { 185 | data: function( elem, i, match ) { 186 | return !!$.data( elem, match[ 3 ] ); 187 | }, 188 | 189 | focusable: function( element ) { 190 | var nodeName = element.nodeName.toLowerCase(), 191 | tabIndex = $.attr( element, "tabindex" ); 192 | if ( "area" === nodeName ) { 193 | var map = element.parentNode, 194 | mapName = map.name, 195 | img; 196 | if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { 197 | return false; 198 | } 199 | img = $( "img[usemap=#" + mapName + "]" )[0]; 200 | return !!img && visible( img ); 201 | } 202 | return ( /input|select|textarea|button|object/.test( nodeName ) 203 | ? !element.disabled 204 | : "a" == nodeName 205 | ? element.href || !isNaN( tabIndex ) 206 | : !isNaN( tabIndex )) 207 | // the element and all of its ancestors must be visible 208 | && visible( element ); 209 | }, 210 | 211 | tabbable: function( element ) { 212 | var tabIndex = $.attr( element, "tabindex" ); 213 | return ( isNaN( tabIndex ) || tabIndex >= 0 ) && $( element ).is( ":focusable" ); 214 | } 215 | }); 216 | 217 | // support 218 | $(function() { 219 | var body = document.body, 220 | div = body.appendChild( div = document.createElement( "div" ) ); 221 | 222 | $.extend( div.style, { 223 | minHeight: "100px", 224 | height: "auto", 225 | padding: 0, 226 | borderWidth: 0 227 | }); 228 | 229 | $.support.minHeight = div.offsetHeight === 100; 230 | $.support.selectstart = "onselectstart" in div; 231 | 232 | // set display to none to avoid a layout bug in IE 233 | // http://dev.jquery.com/ticket/4014 234 | body.removeChild( div ).style.display = "none"; 235 | }); 236 | 237 | 238 | 239 | 240 | 241 | // deprecated 242 | $.extend( $.ui, { 243 | // $.ui.plugin is deprecated. Use the proxy pattern instead. 244 | plugin: { 245 | add: function( module, option, set ) { 246 | var proto = $.ui[ module ].prototype; 247 | for ( var i in set ) { 248 | proto.plugins[ i ] = proto.plugins[ i ] || []; 249 | proto.plugins[ i ].push( [ option, set[ i ] ] ); 250 | } 251 | }, 252 | call: function( instance, name, args ) { 253 | var set = instance.plugins[ name ]; 254 | if ( !set || !instance.element[ 0 ].parentNode ) { 255 | return; 256 | } 257 | 258 | for ( var i = 0; i < set.length; i++ ) { 259 | if ( instance.options[ set[ i ][ 0 ] ] ) { 260 | set[ i ][ 1 ].apply( instance.element, args ); 261 | } 262 | } 263 | } 264 | }, 265 | 266 | // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() 267 | contains: function( a, b ) { 268 | return document.compareDocumentPosition ? 269 | a.compareDocumentPosition( b ) & 16 : 270 | a !== b && a.contains( b ); 271 | }, 272 | 273 | // only used by resizable 274 | hasScroll: function( el, a ) { 275 | 276 | //If overflow is hidden, the element might have extra content, but the user wants to hide it 277 | if ( $( el ).css( "overflow" ) === "hidden") { 278 | return false; 279 | } 280 | 281 | var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", 282 | has = false; 283 | 284 | if ( el[ scroll ] > 0 ) { 285 | return true; 286 | } 287 | 288 | // TODO: determine which cases actually cause this to happen 289 | // if the element doesn't have the scroll set, see if it's possible to 290 | // set the scroll 291 | el[ scroll ] = 1; 292 | has = ( el[ scroll ] > 0 ); 293 | el[ scroll ] = 0; 294 | return has; 295 | }, 296 | 297 | // these are odd functions, fix the API or move into individual plugins 298 | isOverAxis: function( x, reference, size ) { 299 | //Determines when x coordinate is over "b" element axis 300 | return ( x > reference ) && ( x < ( reference + size ) ); 301 | }, 302 | isOver: function( y, x, top, left, height, width ) { 303 | //Determines when x, y coordinates is over "b" element 304 | return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); 305 | } 306 | }); 307 | 308 | })( jQuery ); 309 | -------------------------------------------------------------------------------- /tests/assets/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI Widget 1.8.12 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Widget 9 | */ 10 | (function( $, undefined ) { 11 | 12 | // jQuery 1.4+ 13 | if ( $.cleanData ) { 14 | var _cleanData = $.cleanData; 15 | $.cleanData = function( elems ) { 16 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 17 | $( elem ).triggerHandler( "remove" ); 18 | } 19 | _cleanData( elems ); 20 | }; 21 | } else { 22 | var _remove = $.fn.remove; 23 | $.fn.remove = function( selector, keepData ) { 24 | return this.each(function() { 25 | if ( !keepData ) { 26 | if ( !selector || $.filter( selector, [ this ] ).length ) { 27 | $( "*", this ).add( [ this ] ).each(function() { 28 | $( this ).triggerHandler( "remove" ); 29 | }); 30 | } 31 | } 32 | return _remove.call( $(this), selector, keepData ); 33 | }); 34 | }; 35 | } 36 | 37 | $.widget = function( name, base, prototype ) { 38 | var namespace = name.split( "." )[ 0 ], 39 | fullName; 40 | name = name.split( "." )[ 1 ]; 41 | fullName = namespace + "-" + name; 42 | 43 | if ( !prototype ) { 44 | prototype = base; 45 | base = $.Widget; 46 | } 47 | 48 | // create selector for plugin 49 | $.expr[ ":" ][ fullName ] = function( elem ) { 50 | return !!$.data( elem, name ); 51 | }; 52 | 53 | $[ namespace ] = $[ namespace ] || {}; 54 | $[ namespace ][ name ] = function( options, element ) { 55 | // allow instantiation without initializing for simple inheritance 56 | if ( arguments.length ) { 57 | this._createWidget( options, element ); 58 | } 59 | }; 60 | 61 | var basePrototype = new base(); 62 | // we need to make the options hash a property directly on the new instance 63 | // otherwise we'll modify the options hash on the prototype that we're 64 | // inheriting from 65 | // $.each( basePrototype, function( key, val ) { 66 | // if ( $.isPlainObject(val) ) { 67 | // basePrototype[ key ] = $.extend( {}, val ); 68 | // } 69 | // }); 70 | basePrototype.options = $.extend( true, {}, basePrototype.options ); 71 | $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 72 | namespace: namespace, 73 | widgetName: name, 74 | widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 75 | widgetBaseClass: fullName 76 | }, prototype ); 77 | 78 | $.widget.bridge( name, $[ namespace ][ name ] ); 79 | }; 80 | 81 | $.widget.bridge = function( name, object ) { 82 | $.fn[ name ] = function( options ) { 83 | var isMethodCall = typeof options === "string", 84 | args = Array.prototype.slice.call( arguments, 1 ), 85 | returnValue = this; 86 | 87 | // allow multiple hashes to be passed on init 88 | options = !isMethodCall && args.length ? 89 | $.extend.apply( null, [ true, options ].concat(args) ) : 90 | options; 91 | 92 | // prevent calls to internal methods 93 | if ( isMethodCall && options.charAt( 0 ) === "_" ) { 94 | return returnValue; 95 | } 96 | 97 | if ( isMethodCall ) { 98 | this.each(function() { 99 | var instance = $.data( this, name ), 100 | methodValue = instance && $.isFunction( instance[options] ) ? 101 | instance[ options ].apply( instance, args ) : 102 | instance; 103 | // TODO: add this back in 1.9 and use $.error() (see #5972) 104 | // if ( !instance ) { 105 | // throw "cannot call methods on " + name + " prior to initialization; " + 106 | // "attempted to call method '" + options + "'"; 107 | // } 108 | // if ( !$.isFunction( instance[options] ) ) { 109 | // throw "no such method '" + options + "' for " + name + " widget instance"; 110 | // } 111 | // var methodValue = instance[ options ].apply( instance, args ); 112 | if ( methodValue !== instance && methodValue !== undefined ) { 113 | returnValue = methodValue; 114 | return false; 115 | } 116 | }); 117 | } else { 118 | this.each(function() { 119 | var instance = $.data( this, name ); 120 | if ( instance ) { 121 | instance.option( options || {} )._init(); 122 | } else { 123 | $.data( this, name, new object( options, this ) ); 124 | } 125 | }); 126 | } 127 | 128 | return returnValue; 129 | }; 130 | }; 131 | 132 | $.Widget = function( options, element ) { 133 | // allow instantiation without initializing for simple inheritance 134 | if ( arguments.length ) { 135 | this._createWidget( options, element ); 136 | } 137 | }; 138 | 139 | $.Widget.prototype = { 140 | widgetName: "widget", 141 | widgetEventPrefix: "", 142 | options: { 143 | disabled: false 144 | }, 145 | _createWidget: function( options, element ) { 146 | // $.widget.bridge stores the plugin instance, but we do it anyway 147 | // so that it's stored even before the _create function runs 148 | $.data( element, this.widgetName, this ); 149 | this.element = $( element ); 150 | this.options = $.extend( true, {}, 151 | this.options, 152 | this._getCreateOptions(), 153 | options ); 154 | 155 | var self = this; 156 | this.element.bind( "remove." + this.widgetName, function() { 157 | self.destroy(); 158 | }); 159 | 160 | this._create(); 161 | this._trigger( "create" ); 162 | this._init(); 163 | }, 164 | _getCreateOptions: function() { 165 | return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; 166 | }, 167 | _create: function() {}, 168 | _init: function() {}, 169 | 170 | destroy: function() { 171 | this.element 172 | .unbind( "." + this.widgetName ) 173 | .removeData( this.widgetName ); 174 | this.widget() 175 | .unbind( "." + this.widgetName ) 176 | .removeAttr( "aria-disabled" ) 177 | .removeClass( 178 | this.widgetBaseClass + "-disabled " + 179 | "ui-state-disabled" ); 180 | }, 181 | 182 | widget: function() { 183 | return this.element; 184 | }, 185 | 186 | option: function( key, value ) { 187 | var options = key; 188 | 189 | if ( arguments.length === 0 ) { 190 | // don't return a reference to the internal hash 191 | return $.extend( {}, this.options ); 192 | } 193 | 194 | if (typeof key === "string" ) { 195 | if ( value === undefined ) { 196 | return this.options[ key ]; 197 | } 198 | options = {}; 199 | options[ key ] = value; 200 | } 201 | 202 | this._setOptions( options ); 203 | 204 | return this; 205 | }, 206 | _setOptions: function( options ) { 207 | var self = this; 208 | $.each( options, function( key, value ) { 209 | self._setOption( key, value ); 210 | }); 211 | 212 | return this; 213 | }, 214 | _setOption: function( key, value ) { 215 | this.options[ key ] = value; 216 | 217 | if ( key === "disabled" ) { 218 | this.widget() 219 | [ value ? "addClass" : "removeClass"]( 220 | this.widgetBaseClass + "-disabled" + " " + 221 | "ui-state-disabled" ) 222 | .attr( "aria-disabled", value ); 223 | } 224 | 225 | return this; 226 | }, 227 | 228 | enable: function() { 229 | return this._setOption( "disabled", false ); 230 | }, 231 | disable: function() { 232 | return this._setOption( "disabled", true ); 233 | }, 234 | 235 | _trigger: function( type, event, data ) { 236 | var callback = this.options[ type ]; 237 | 238 | event = $.Event( event ); 239 | event.type = ( type === this.widgetEventPrefix ? 240 | type : 241 | this.widgetEventPrefix + type ).toLowerCase(); 242 | data = data || {}; 243 | 244 | // copy original event properties over to the new event 245 | // this would happen if we could call $.event.fix instead of $.Event 246 | // but we don't have a way to force an event to be fixed multiple times 247 | if ( event.originalEvent ) { 248 | for ( var i = $.event.props.length, prop; i; ) { 249 | prop = $.event.props[ --i ]; 250 | event[ prop ] = event.originalEvent[ prop ]; 251 | } 252 | } 253 | 254 | this.element.trigger( event, data ); 255 | 256 | return !( $.isFunction(callback) && 257 | callback.call( this.element[0], event, data ) === false || 258 | event.isDefaultPrevented() ); 259 | } 260 | }; 261 | 262 | })( jQuery ); 263 | -------------------------------------------------------------------------------- /tests/assets/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2009-08-17 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | This file creates a global JSON object containing two methods: stringify 12 | and parse. 13 | 14 | JSON.stringify(value, replacer, space) 15 | value any JavaScript value, usually an object or array. 16 | 17 | replacer an optional parameter that determines how object 18 | values are stringified for objects. It can be a 19 | function or an array of strings. 20 | 21 | space an optional parameter that specifies the indentation 22 | of nested structures. If it is omitted, the text will 23 | be packed without extra whitespace. If it is a number, 24 | it will specify the number of spaces to indent at each 25 | level. If it is a string (such as '\t' or ' '), 26 | it contains the characters used to indent at each level. 27 | 28 | This method produces a JSON text from a JavaScript value. 29 | 30 | When an object value is found, if the object contains a toJSON 31 | method, its toJSON method will be called and the result will be 32 | stringified. A toJSON method does not serialize: it returns the 33 | value represented by the name/value pair that should be serialized, 34 | or undefined if nothing should be serialized. The toJSON method 35 | will be passed the key associated with the value, and this will be 36 | bound to the value 37 | 38 | For example, this would serialize Dates as ISO strings. 39 | 40 | Date.prototype.toJSON = function (key) { 41 | function f(n) { 42 | // Format integers to have at least two digits. 43 | return n < 10 ? '0' + n : n; 44 | } 45 | 46 | return this.getUTCFullYear() + '-' + 47 | f(this.getUTCMonth() + 1) + '-' + 48 | f(this.getUTCDate()) + 'T' + 49 | f(this.getUTCHours()) + ':' + 50 | f(this.getUTCMinutes()) + ':' + 51 | f(this.getUTCSeconds()) + 'Z'; 52 | }; 53 | 54 | You can provide an optional replacer method. It will be passed the 55 | key and value of each member, with this bound to the containing 56 | object. The value that is returned from your method will be 57 | serialized. If your method returns undefined, then the member will 58 | be excluded from the serialization. 59 | 60 | If the replacer parameter is an array of strings, then it will be 61 | used to select the members to be serialized. It filters the results 62 | such that only members with keys listed in the replacer array are 63 | stringified. 64 | 65 | Values that do not have JSON representations, such as undefined or 66 | functions, will not be serialized. Such values in objects will be 67 | dropped; in arrays they will be replaced with null. You can use 68 | a replacer function to replace those with JSON values. 69 | JSON.stringify(undefined) returns undefined. 70 | 71 | The optional space parameter produces a stringification of the 72 | value that is filled with line breaks and indentation to make it 73 | easier to read. 74 | 75 | If the space parameter is a non-empty string, then that string will 76 | be used for indentation. If the space parameter is a number, then 77 | the indentation will be that many spaces. 78 | 79 | Example: 80 | 81 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 82 | // text is '["e",{"pluribus":"unum"}]' 83 | 84 | 85 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 86 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 87 | 88 | text = JSON.stringify([new Date()], function (key, value) { 89 | return this[key] instanceof Date ? 90 | 'Date(' + this[key] + ')' : value; 91 | }); 92 | // text is '["Date(---current time---)"]' 93 | 94 | 95 | JSON.parse(text, reviver) 96 | This method parses a JSON text to produce an object or array. 97 | It can throw a SyntaxError exception. 98 | 99 | The optional reviver parameter is a function that can filter and 100 | transform the results. It receives each of the keys and values, 101 | and its return value is used instead of the original value. 102 | If it returns what it received, then the structure is not modified. 103 | If it returns undefined then the member is deleted. 104 | 105 | Example: 106 | 107 | // Parse the text. Values that look like ISO date strings will 108 | // be converted to Date objects. 109 | 110 | myData = JSON.parse(text, function (key, value) { 111 | var a; 112 | if (typeof value === 'string') { 113 | a = 114 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 115 | if (a) { 116 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 117 | +a[5], +a[6])); 118 | } 119 | } 120 | return value; 121 | }); 122 | 123 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 124 | var d; 125 | if (typeof value === 'string' && 126 | value.slice(0, 5) === 'Date(' && 127 | value.slice(-1) === ')') { 128 | d = new Date(value.slice(5, -1)); 129 | if (d) { 130 | return d; 131 | } 132 | } 133 | return value; 134 | }); 135 | 136 | 137 | This is a reference implementation. You are free to copy, modify, or 138 | redistribute. 139 | 140 | This code should be minified before deployment. 141 | See http://javascript.crockford.com/jsmin.html 142 | 143 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 144 | NOT CONTROL. 145 | */ 146 | 147 | /*jslint evil: true */ 148 | 149 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 150 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 151 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 152 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 153 | test, toJSON, toString, valueOf 154 | */ 155 | 156 | "use strict"; 157 | 158 | // Create a JSON object only if one does not already exist. We create the 159 | // methods in a closure to avoid creating global variables. 160 | 161 | if (!this.JSON) { 162 | this.JSON = {}; 163 | } 164 | 165 | (function () { 166 | 167 | function f(n) { 168 | // Format integers to have at least two digits. 169 | return n < 10 ? '0' + n : n; 170 | } 171 | 172 | if (typeof Date.prototype.toJSON !== 'function') { 173 | 174 | Date.prototype.toJSON = function (key) { 175 | 176 | return isFinite(this.valueOf()) ? 177 | this.getUTCFullYear() + '-' + 178 | f(this.getUTCMonth() + 1) + '-' + 179 | f(this.getUTCDate()) + 'T' + 180 | f(this.getUTCHours()) + ':' + 181 | f(this.getUTCMinutes()) + ':' + 182 | f(this.getUTCSeconds()) + 'Z' : null; 183 | }; 184 | 185 | String.prototype.toJSON = 186 | Number.prototype.toJSON = 187 | Boolean.prototype.toJSON = function (key) { 188 | return this.valueOf(); 189 | }; 190 | } 191 | 192 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 193 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | gap, 195 | indent, 196 | meta = { // table of character substitutions 197 | '\b': '\\b', 198 | '\t': '\\t', 199 | '\n': '\\n', 200 | '\f': '\\f', 201 | '\r': '\\r', 202 | '"' : '\\"', 203 | '\\': '\\\\' 204 | }, 205 | rep; 206 | 207 | 208 | function quote(string) { 209 | 210 | // If the string contains no control characters, no quote characters, and no 211 | // backslash characters, then we can safely slap some quotes around it. 212 | // Otherwise we must also replace the offending characters with safe escape 213 | // sequences. 214 | 215 | escapable.lastIndex = 0; 216 | return escapable.test(string) ? 217 | '"' + string.replace(escapable, function (a) { 218 | var c = meta[a]; 219 | return typeof c === 'string' ? c : 220 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 221 | }) + '"' : 222 | '"' + string + '"'; 223 | } 224 | 225 | 226 | function str(key, holder) { 227 | // Produce a string from holder[key]. 228 | 229 | var i, // The loop counter. 230 | k, // The member key. 231 | v, // The member value. 232 | length, 233 | mind = gap, 234 | partial, 235 | value = holder[key]; 236 | 237 | // If the value has a toJSON method, call it to obtain a replacement value. 238 | 239 | if (value && typeof value === 'object' && 240 | typeof value.toJSON === 'function') { 241 | value = value.toJSON(key); 242 | } 243 | 244 | // If we were called with a replacer function, then call the replacer to 245 | // obtain a replacement value. 246 | 247 | if (typeof rep === 'function') { 248 | value = rep.call(holder, key, value); 249 | } 250 | 251 | // What happens next depends on the value's type. 252 | 253 | switch (typeof value) { 254 | case 'string': 255 | return quote(value); 256 | 257 | case 'number': 258 | 259 | // JSON numbers must be finite. Encode non-finite numbers as null. 260 | 261 | return isFinite(value) ? String(value) : 'null'; 262 | 263 | case 'boolean': 264 | case 'null': 265 | 266 | // If the value is a boolean or null, convert it to a string. Note: 267 | // typeof null does not produce 'null'. The case is included here in 268 | // the remote chance that this gets fixed someday. 269 | 270 | return String(value); 271 | 272 | // If the type is 'object', we might be dealing with an object or an array or 273 | // null. 274 | 275 | case 'object': 276 | 277 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 278 | // so watch out for that case. 279 | 280 | if (!value) { 281 | return 'null'; 282 | } 283 | 284 | // Make an array to hold the partial results of stringifying this object value. 285 | 286 | gap += indent; 287 | partial = []; 288 | 289 | // Is the value an array? 290 | 291 | if (Object.prototype.toString.apply(value) === '[object Array]') { 292 | 293 | // The value is an array. Stringify every element. Use null as a placeholder 294 | // for non-JSON values. 295 | 296 | length = value.length; 297 | for (i = 0; i < length; i += 1) { 298 | partial[i] = str(i, value) || 'null'; 299 | } 300 | 301 | // Join all of the elements together, separated with commas, and wrap them in 302 | // brackets. 303 | 304 | v = partial.length === 0 ? '[]' : 305 | gap ? '[\n' + gap + 306 | partial.join(',\n' + gap) + '\n' + 307 | mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | k = rep[i]; 319 | if (typeof k === 'string') { 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : 344 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 345 | mind + '}' : '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | // The stringify method takes a value and an optional replacer, and an optional 356 | // space parameter, and returns a JSON text. The replacer can be a function 357 | // that can replace values, or an array of strings that will select the keys. 358 | // A default replacer method can be provided. Use of the space parameter can 359 | // produce text that is more easily readable. 360 | 361 | var i; 362 | gap = ''; 363 | indent = ''; 364 | 365 | // If the space parameter is a number, make an indent string containing that 366 | // many spaces. 367 | 368 | if (typeof space === 'number') { 369 | for (i = 0; i < space; i += 1) { 370 | indent += ' '; 371 | } 372 | 373 | // If the space parameter is a string, it will be used as the indent string. 374 | 375 | } else if (typeof space === 'string') { 376 | indent = space; 377 | } 378 | 379 | // If there is a replacer, it must be a function or an array. 380 | // Otherwise, throw an error. 381 | 382 | rep = replacer; 383 | if (replacer && typeof replacer !== 'function' && 384 | (typeof replacer !== 'object' || 385 | typeof replacer.length !== 'number')) { 386 | throw new Error('JSON.stringify'); 387 | } 388 | 389 | // Make a fake root object containing our value under the key of ''. 390 | // Return the result of stringifying the value. 391 | 392 | return str('', {'': value}); 393 | }; 394 | } 395 | 396 | 397 | // If the JSON object does not yet have a parse method, give it one. 398 | 399 | if (typeof JSON.parse !== 'function') { 400 | JSON.parse = function (text, reviver) { 401 | 402 | // The parse method takes a text and an optional reviver function, and returns 403 | // a JavaScript value if the text is a valid JSON text. 404 | 405 | var j; 406 | 407 | function walk(holder, key) { 408 | 409 | // The walk method is used to recursively walk the resulting structure so 410 | // that modifications can be made. 411 | 412 | var k, v, value = holder[key]; 413 | if (value && typeof value === 'object') { 414 | for (k in value) { 415 | if (Object.hasOwnProperty.call(value, k)) { 416 | v = walk(value, k); 417 | if (v !== undefined) { 418 | value[k] = v; 419 | } else { 420 | delete value[k]; 421 | } 422 | } 423 | } 424 | } 425 | return reviver.call(holder, key, value); 426 | } 427 | 428 | 429 | // Parsing happens in four stages. In the first stage, we replace certain 430 | // Unicode characters with escape sequences. JavaScript handles many characters 431 | // incorrectly, either silently deleting them, or treating them as line endings. 432 | 433 | cx.lastIndex = 0; 434 | if (cx.test(text)) { 435 | text = text.replace(cx, function (a) { 436 | return '\\u' + 437 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 438 | }); 439 | } 440 | 441 | // In the second stage, we run the text against regular expressions that look 442 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 443 | // because they can cause invocation, and '=' because it can cause mutation. 444 | // But just to be safe, we want to reject all unexpected forms. 445 | 446 | // We split the second stage into 4 regexp operations in order to work around 447 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 448 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 449 | // replace all simple value tokens with ']' characters. Third, we delete all 450 | // open brackets that follow a colon or comma or that begin the text. Finally, 451 | // we look to see that the remaining characters are only whitespace or ']' or 452 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 453 | 454 | if (/^[\],:{}\s]*$/. 455 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 456 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 457 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 458 | 459 | // In the third stage we use the eval function to compile the text into a 460 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 461 | // in JavaScript: it can begin a block or an object literal. We wrap the text 462 | // in parens to eliminate the ambiguity. 463 | 464 | j = eval('(' + text + ')'); 465 | 466 | // In the optional fourth stage, we recursively walk the new structure, passing 467 | // each name/value pair to a reviver function for possible transformation. 468 | 469 | return typeof reviver === 'function' ? 470 | walk({'': j}, '') : j; 471 | } 472 | 473 | // If the text is not JSON parseable, then a SyntaxError is thrown. 474 | 475 | throw new SyntaxError('JSON.parse'); 476 | }; 477 | } 478 | }()); 479 | -------------------------------------------------------------------------------- /tests/runner.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Bigpipe JS Test 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 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/specs/bigpipe-spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Bigpipe JS 4 | * https://github.com/orygens/bigpipe-js 5 | * 6 | * Licensed under the MIT license: 7 | * http://www.opensource.org/licenses/mit-license 8 | * Copyright (c) 2011 Orygens contato@orygens.com 9 | * 10 | */ 11 | 12 | describe("The BigPipe", function() { 13 | 14 | describe("when use it without parameters", function() { 15 | 16 | beforeEach(function() { 17 | this.container = $("
"); 18 | $(this.container).BigPipe(); 19 | }); 20 | 21 | it("should be able to call without parameters", function() { 22 | expect(this.container.data("BigPipe")).toBeTruthy(); 23 | }); 24 | 25 | describe("the bigpipe object", function() { 26 | 27 | beforeEach(function() { 28 | this.bigpipe = this.container.data("BigPipe"); 29 | }); 30 | 31 | it("should have a pagelets option to store all pagelets that should be executed", function() { 32 | expect(this.bigpipe.options.pagelets).toBeDefined(); 33 | }); 34 | 35 | describe("the pagelets option", function() { 36 | 37 | it("should be initialized with a empty array", function() { 38 | expect(this.bigpipe.options.pagelets).toEqual([]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | 45 | }); 46 | 47 | describe("when use with an unique pagelet with content", function() { 48 | 49 | beforeEach(function() { 50 | var html = [ 51 | "
", 52 | "
", 53 | "
" 54 | ]; 55 | 56 | var pagelet = { 57 | id: "mypagelet", 58 | content: "hello world!", 59 | css: [], 60 | js: [] 61 | }; 62 | 63 | this.container = $(html.join("")); 64 | 65 | this.cssCallback = jasmine.createSpy(); 66 | this.container.bind("loadCSS", this.cssCallback); 67 | 68 | $.fn.Pagelet = jasmine.createSpy(); 69 | 70 | $(this.container).BigPipe({pagelets:[pagelet]}); 71 | }); 72 | 73 | it("should trigger the loadCSS event", function() { 74 | expect(this.cssCallback).toHaveBeenCalled(); 75 | }); 76 | 77 | describe("when load all css files", function() { 78 | 79 | beforeEach(function() { 80 | this.contentCallback = jasmine.createSpy(); 81 | this.container.bind("loadContent", this.contentCallback); 82 | 83 | this.container.trigger("cssLoaded"); 84 | }); 85 | 86 | it("should call loadContent event", function() { 87 | expect(this.contentCallback).toHaveBeenCalled(); 88 | }); 89 | 90 | describe("when load all content", function() { 91 | 92 | beforeEach(function() { 93 | this.jsCallback = jasmine.createSpy(); 94 | this.container.bind("loadJS", this.jsCallback); 95 | 96 | this.container.trigger("contentLoaded"); 97 | }); 98 | 99 | it("should call loadJS event", function() { 100 | expect(this.jsCallback).toHaveBeenCalled(); 101 | }); 102 | 103 | describe("when load all js", function() { 104 | 105 | beforeEach(function() { 106 | this.onloadCallback = jasmine.createSpy(); 107 | this.container.bind("onLoad", this.onloadCallback); 108 | 109 | this.container.trigger("jsLoaded"); 110 | }); 111 | 112 | it("should call onload event", function() { 113 | expect(this.onloadCallback).toHaveBeenCalled(); 114 | }); 115 | 116 | }); 117 | 118 | }); 119 | 120 | }); 121 | 122 | }); 123 | 124 | describe("when use two pagelets", function() { 125 | 126 | beforeEach(function() { 127 | var html = [ 128 | "
", 129 | "
" 130 | ]; 131 | 132 | var pagelet = { 133 | id: "mypagelet", 134 | content: "hello world!", 135 | css: [], 136 | js: [] 137 | }; 138 | 139 | this.container = $(html.join("")); 140 | 141 | this.cssCallback = jasmine.createSpy(); 142 | this.container.bind("loadCSS", this.cssCallback); 143 | 144 | $.fn.Pagelet = jasmine.createSpy(); 145 | 146 | $(this.container).BigPipe({pagelets:[pagelet, pagelet]}); 147 | }); 148 | 149 | describe("when load one css file", function() { 150 | 151 | beforeEach(function() { 152 | this.contentCallback = jasmine.createSpy(); 153 | this.container.bind("loadContent", this.contentCallback); 154 | 155 | this.container.trigger("cssLoaded"); 156 | }); 157 | 158 | it("should not call loadContent event before all css files is loaded", function() { 159 | expect(this.contentCallback).not.toHaveBeenCalled(); 160 | }); 161 | 162 | describe("when load all CSS", function() { 163 | 164 | beforeEach(function() { 165 | this.container.trigger("cssLoaded"); 166 | }); 167 | 168 | it("should call loadContent event", function() { 169 | expect(this.contentCallback).toHaveBeenCalled(); 170 | }); 171 | 172 | }); 173 | 174 | }); 175 | 176 | }); 177 | 178 | }); 179 | 180 | -------------------------------------------------------------------------------- /tests/specs/pagelet-spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orygens/bigpipe-js/011242dbc5b2466a5c82701e91c9223d866f3fc2/tests/specs/pagelet-spec.js --------------------------------------------------------------------------------