├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── bin └── jasmine-dom ├── examples ├── config.yaml ├── lib │ ├── jasmine │ │ ├── jasmine-html.js │ │ ├── jasmine.css │ │ └── jasmine.js │ └── jquery │ │ └── jquery.js ├── regressionRunner.html ├── runner-fail.html ├── runner-pass.html ├── spec │ ├── example-fail_spec.js │ ├── example-pass-dom_spec.js │ ├── example-pass_spec.js │ └── regression_spec.js └── src │ ├── add-in-dom.js │ ├── divide.js │ ├── multiply.js │ └── regression.js ├── lib ├── args2options.js └── jasmine-dom │ ├── index.js │ ├── reporter-agregator.js │ ├── reporter-detailed.js │ ├── reporter-html.js │ ├── reporter-junit.js │ ├── reporter-simple.js │ ├── resources │ ├── jasmine-html.js │ ├── jasmine.css │ ├── jasmine.js │ ├── jquery.js │ ├── simple.html │ └── skeleton.html │ ├── runner-from-html.js │ └── server.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | *.iml 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | - '0.11' 5 | - '0.10' 6 | script: 7 | - npm test 8 | deploy: 9 | provider: npm 10 | email: andrewpmckenzie@gmail.com 11 | api_key: 12 | secure: gqPyyQXPvYAdL1R4enAgjl++wrB4QYy1HHTd88g7pC4DcSaFMogSB1HVccTwyUv48pKVrt5L1e543+sfobuNJNvW3hmHr8YX6UaE2w9UUtoBtYrht9D/KvPO/Fa608IZ8TPUg0Rdp9XLngNLq5DkEymDaxGxkGojmnssEuoTMx4= 13 | on: 14 | tags: true 15 | repo: andrewpmckenzie/node-jasmine-dom 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-jasmine-dom [![Build Status](https://travis-ci.org/andrewpmckenzie/node-jasmine-dom.svg?branch=master)](https://travis-ci.org/andrewpmckenzie/node-jasmine-dom) 2 | ================ 3 | 4 | Run your browser-based [jasmine][1] specs headless with [node.js][2]. Then 5 | output in one of many formats such as JSON or JUnit XML (perfect 6 | for integration with CI servers like [Jenkins][3]). 7 | 8 | installation 9 | ------------ 10 | `npm install -g jasmine-dom` 11 | 12 | usage 13 | ----- 14 | Construct your SpecRunner.html as shown in the jasmine examples, then run: 15 | 16 | jasmine-dom --runner path/to/SpecRunner.html 17 | 18 | or 19 | 20 | jasmine-dom --config path/to/config.yaml 21 | 22 | You can optionally provide the following arguments: 23 | 24 | * --help, provides usage information 25 | * --format simple|detailed|nice|json|html|junit, displays the result in the specified format 26 | * --output path, writes the output to the specified file 27 | * --server [port], serves a simple (but effective) page showing the current state 28 | of the tests. You can also specify an optional --refresh 29 | intervalInMS argument to specify the wait between running 30 | the tests (because the server is constantly running 'em). 31 | * --routeconsole, calls to window.console.log (or error, warn, info) within the tests / tested code 32 | will be displayed in the terminal output 33 | 34 | server 35 | ------ 36 | 37 | jasmine-dom --runner examples/runner.html --server 8090 --refresh 3000 38 | 39 | will run a server on http://localhost:8090/. Here a simple green or red page will reflect the current state 40 | of your tests. The tests will run every 3000ms, and the page ajaximatically updates with the result. 41 | 42 | If you'd like to see the default jasmine html, visit http://localhost:8090/jasmine. Note, the result is still 43 | obtained via the nodejs runner (i.e. it wasn't run in your browser). 44 | 45 | specifying runners 46 | ------------------ 47 | A single runner file can be provided via the --runner command. To specify more than one 48 | runner, use the --config argument and a yaml config file in the format: 49 | 50 | ```yaml 51 | --- 52 | test_one: 53 | name: This is the name of the first set of tests 54 | runner: path/to/runner_1.html 55 | test_two: 56 | name: This is the name of the second set of tests 57 | runner: path/to/another/runner.html 58 | ``` 59 | 60 | The config file allows you to provide names for your runners. These names will be used when identifying failing tests. 61 | 62 | example 1 63 | --------- 64 | jasmine-dom --runner examples/runner.html 65 | 66 | will output: 67 | 68 | Failed. 69 | 70 | example 2 71 | --------- 72 | 73 | jasmine-dom --runner examples/runner.html --format junit --output javascript_results.xml 74 | 75 | will write to javascript_results.xml: 76 | ```xml 77 | 78 | 79 | 80 | 81 | 82 | (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:94:50) 86 | at [object Object].toEqual (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:1138:29) 87 | at [object Object]. (/Users/andrew/development/node-jasmine-dom/examples/tests/spec/example-functions_spec.js:10:13) 88 | at [object Object].execute (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:968:15) 89 | at [object Object].next_ (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:1739:31) 90 | at [object Object].start (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:1692:8) 91 | at [object Object].execute (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:2018:14) 92 | at [object Object].next_ (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:1739:31) 93 | at [object Object].start (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:1692:8) 94 | at [object Object].execute (/Users/andrew/development/node-jasmine-dom/examples/tests/lib/jasmine.js:2163:14) 95 | ]]> 96 | 97 | 98 | 99 | ``` 100 | example 3 101 | --------- 102 | 103 | jasmine-dom --config ./examples/config.yaml --format nice 104 | 105 | with ./examples/config.yaml: 106 | 107 | ```yaml 108 | --- 109 | test_one: 110 | name: Example test one 111 | runner: ./runner.html 112 | test_two: 113 | name: Example test two 114 | runner: ./runner2.html 115 | ``` 116 | 117 | will output: 118 | 119 | ====== FAILED ====== 120 | - In A suite that fails >> Example functions (should fail) >> Should fail!! :: Expected 3 to equal 8. 121 | 122 | example 4 123 | --------- 124 | 125 | jasmine-dom --config ./examples/config.yaml --format detailed 126 | 127 | will output: 128 | 129 | ====== FAILED ====== 130 | 131 | Example functions (should fail) - 2 tests 132 | PASSES 133 | - Should multiply two numbers 134 | FAILURES 135 | - Should fail!! 136 | [Expected 3 to equal 8.] 137 | Example functions that update the DOM - 2 tests 138 | PASSES 139 | - Should add two numbers 140 | - Should divide two numbers 141 | FAILURES 142 | (none) 143 | 144 | ====== FAILED ====== 145 | 146 | 147 | changelog 148 | --------- 149 | |Version|Date |Description | 150 | |-------|-----------|---------------------| 151 | | 1.0.0 | 7-12-2012 | Dusted off the cobwebs. Upgraded jsdom to v1.5.0 to work with modern node versions. Also needed to bump min node to v0.10.38. | 152 | | 0.3.0 | 22-12-2012 | Added 'detailed' report format. Made examples a bit clearer. | 153 | 154 | have you seen **[jasmine-node][5]**? 155 | ------------------------------------ 156 | It's provided a lot of inspiration for this project, and may be just what 157 | you're looking for. If you're not reliant on a DOM, then it's worth checking 158 | out. 159 | 160 | [1]: http://pivotal.github.com/jasmine/ 161 | [2]: http://nodejs.org/ 162 | [3]: http://jenkins-ci.org/ 163 | [4]: http://npmjs.org/ 164 | [5]: https://github.com/mhevery/jasmine-node 165 | -------------------------------------------------------------------------------- /bin/jasmine-dom: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var args2options = require('../lib/args2options'); 4 | var jasmineDom = require('../lib/jasmine-dom'); 5 | 6 | var options = args2options(process.argv); 7 | jasmineDom.run(options); 8 | -------------------------------------------------------------------------------- /examples/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | test_one: 3 | name: A suite that passes 4 | runner: ./runner-pass.html 5 | test_two: 6 | name: A suite that fails 7 | runner: ./runner-fail.html 8 | -------------------------------------------------------------------------------- /examples/lib/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | // NOTE: contains modifications for jasmine-dom 2 | 3 | jasmine.TrivialReporter = function(doc) { 4 | this.document = doc || document; 5 | this.suiteDivs = {}; 6 | this.logRunningSpecs = false; 7 | }; 8 | 9 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 10 | var el = document.createElement(type); 11 | 12 | for (var i = 2; i < arguments.length; i++) { 13 | var child = arguments[i]; 14 | 15 | if (typeof child === 'string') { 16 | el.appendChild(document.createTextNode(child)); 17 | } else { 18 | if (child) { el.appendChild(child); } 19 | } 20 | } 21 | 22 | for (var attr in attrs) { 23 | if (attr == "className") { 24 | el[attr] = attrs[attr]; 25 | } else { 26 | el.setAttribute(attr, attrs[attr]); 27 | } 28 | } 29 | 30 | return el; 31 | }; 32 | 33 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 34 | var showPassed, showSkipped; 35 | 36 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 37 | this.createDom('div', { className: 'banner' }, 38 | this.createDom('div', { className: 'logo' }, 39 | this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"), 40 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 41 | this.createDom('div', { className: 'options' }, 42 | "Show ", 43 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 45 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 46 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 47 | ) 48 | ), 49 | 50 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 51 | this.createDom('span', { className: 'run_spec', href: '?' }, "run all"), 52 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 53 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 54 | ); 55 | 56 | this.document.body.appendChild(this.outerDiv); 57 | 58 | var suites = runner.suites(); 59 | for (var i = 0; i < suites.length; i++) { 60 | var suite = suites[i]; 61 | var suiteDiv = this.createDom('div', { className: 'suite' }, 62 | this.createDom('span', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 63 | this.createDom('span', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 64 | this.suiteDivs[suite.id] = suiteDiv; 65 | var parentDiv = this.outerDiv; 66 | if (suite.parentSuite) { 67 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 68 | } 69 | parentDiv.appendChild(suiteDiv); 70 | } 71 | 72 | this.startedAt = new Date(); 73 | 74 | var self = this; 75 | showPassed.onclick = function(evt) { 76 | if (showPassed.checked) { 77 | self.outerDiv.className += ' show-passed'; 78 | } else { 79 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 80 | } 81 | }; 82 | 83 | showSkipped.onclick = function(evt) { 84 | if (showSkipped.checked) { 85 | self.outerDiv.className += ' show-skipped'; 86 | } else { 87 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 88 | } 89 | }; 90 | }; 91 | 92 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 93 | var results = runner.results(); 94 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 95 | this.runnerDiv.setAttribute("class", className); 96 | //do it twice for IE 97 | this.runnerDiv.setAttribute("className", className); 98 | var specs = runner.specs(); 99 | var specCount = 0; 100 | for (var i = 0; i < specs.length; i++) { 101 | if (this.specFilter(specs[i])) { 102 | specCount++; 103 | } 104 | } 105 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 106 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 107 | this.runnerMessageSpan.replaceChild(this.createDom('span', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 108 | 109 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 110 | }; 111 | 112 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 113 | var results = suite.results(); 114 | var status = results.passed() ? 'passed' : 'failed'; 115 | if (results.totalCount == 0) { // todo: change this to check results.skipped 116 | status = 'skipped'; 117 | } 118 | this.suiteDivs[suite.id].className += " " + status; 119 | }; 120 | 121 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 122 | if (this.logRunningSpecs) { 123 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 124 | } 125 | }; 126 | 127 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 128 | var results = spec.results(); 129 | var status = results.passed() ? 'passed' : 'failed'; 130 | if (results.skipped) { 131 | status = 'skipped'; 132 | } 133 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 134 | this.createDom('span', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 135 | this.createDom('span', { 136 | className: 'description', 137 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 138 | title: spec.getFullName() 139 | }, spec.description)); 140 | 141 | 142 | var resultItems = results.getItems(); 143 | var messagesDiv = this.createDom('div', { className: 'messages' }); 144 | for (var i = 0; i < resultItems.length; i++) { 145 | var result = resultItems[i]; 146 | 147 | if (result.type == 'log') { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 149 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 150 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 151 | 152 | if (result.trace.stack) { 153 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 154 | } 155 | } 156 | } 157 | 158 | if (messagesDiv.childNodes.length > 0) { 159 | specDiv.appendChild(messagesDiv); 160 | } 161 | 162 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 163 | }; 164 | 165 | jasmine.TrivialReporter.prototype.log = function() { 166 | var console = jasmine.getGlobal().console; 167 | if (console && console.log) { 168 | if (console.log.apply) { 169 | console.log.apply(console, arguments); 170 | } else { 171 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 172 | } 173 | } 174 | }; 175 | 176 | jasmine.TrivialReporter.prototype.getLocation = function() { 177 | return this.document.location; 178 | }; 179 | 180 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 181 | 182 | return true; 183 | }; 184 | -------------------------------------------------------------------------------- /examples/lib/jasmine/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 | visibility:hidden; 49 | text-align: right; 50 | font-size: .8em; 51 | } 52 | 53 | 54 | 55 | 56 | .suite { 57 | border: 1px outset gray; 58 | margin: 5px 0; 59 | padding-left: 1em; 60 | } 61 | 62 | .suite .suite { 63 | margin: 5px; 64 | } 65 | 66 | .suite.passed { 67 | background-color: #dfd; 68 | } 69 | 70 | .suite.failed { 71 | background-color: #fdd; 72 | } 73 | 74 | .spec { 75 | margin: 5px; 76 | padding-left: 1em; 77 | clear: both; 78 | } 79 | 80 | .spec.failed, .spec.passed, .spec.skipped { 81 | padding-bottom: 5px; 82 | border: 1px solid gray; 83 | } 84 | 85 | .spec.failed { 86 | background-color: #fbb; 87 | border-color: red; 88 | } 89 | 90 | .spec.passed { 91 | background-color: #bfb; 92 | border-color: green; 93 | } 94 | 95 | .spec.skipped { 96 | background-color: #bbb; 97 | } 98 | 99 | .messages { 100 | border-left: 1px dashed gray; 101 | padding-left: 1em; 102 | padding-right: 1em; 103 | } 104 | 105 | .passed { 106 | background-color: #cfc; 107 | display: none; 108 | } 109 | 110 | .failed { 111 | background-color: #fbb; 112 | } 113 | 114 | .skipped { 115 | color: #777; 116 | background-color: #eee; 117 | display: none; 118 | } 119 | 120 | 121 | /*.resultMessage {*/ 122 | /*white-space: pre;*/ 123 | /*}*/ 124 | 125 | .resultMessage span.result { 126 | display: block; 127 | line-height: 2em; 128 | color: black; 129 | } 130 | 131 | .resultMessage .mismatch { 132 | color: black; 133 | } 134 | 135 | .stackTrace { 136 | white-space: pre; 137 | font-size: .8em; 138 | margin-left: 10px; 139 | max-height: 5em; 140 | overflow: auto; 141 | border: 1px inset red; 142 | padding: 1em; 143 | background: #eef; 144 | } 145 | 146 | .finished-at { 147 | padding-left: 1em; 148 | font-size: .6em; 149 | } 150 | 151 | .show-passed .passed, 152 | .show-skipped .skipped { 153 | display: block; 154 | } 155 | 156 | 157 | #jasmine_content { 158 | position:fixed; 159 | right: 100%; 160 | } 161 | 162 | .runner { 163 | border: 1px solid gray; 164 | display: block; 165 | margin: 5px 0; 166 | padding: 2px 0 2px 10px; 167 | } 168 | -------------------------------------------------------------------------------- /examples/lib/jasmine/jasmine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 3 | * 4 | * @namespace 5 | */ 6 | var jasmine = {}; 7 | 8 | /** 9 | * @private 10 | */ 11 | jasmine.unimplementedMethod_ = function() { 12 | throw new Error("unimplemented method"); 13 | }; 14 | 15 | /** 16 | * Use jasmine.undefined instead of undefined, since undefined is just 17 | * a plain old variable and may be redefined by somebody else. 18 | * 19 | * @private 20 | */ 21 | jasmine.undefined = jasmine.___undefined___; 22 | 23 | /** 24 | * 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. 25 | * 26 | */ 27 | jasmine.DEFAULT_UPDATE_INTERVAL = 250; 28 | 29 | /** 30 | * Default timeout interval in milliseconds for waitsFor() blocks. 31 | */ 32 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; 33 | 34 | jasmine.getGlobal = function() { 35 | function getGlobal() { 36 | return this; 37 | } 38 | 39 | return getGlobal(); 40 | }; 41 | 42 | /** 43 | * Allows for bound functions to be compared. Internal use only. 44 | * 45 | * @ignore 46 | * @private 47 | * @param base {Object} bound 'this' for the function 48 | * @param name {Function} function to find 49 | */ 50 | jasmine.bindOriginal_ = function(base, name) { 51 | var original = base[name]; 52 | if (original.apply) { 53 | return function() { 54 | return original.apply(base, arguments); 55 | }; 56 | } else { 57 | // IE support 58 | return jasmine.getGlobal()[name]; 59 | } 60 | }; 61 | 62 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 63 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 64 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 65 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 66 | 67 | jasmine.MessageResult = function(values) { 68 | this.type = 'log'; 69 | this.values = values; 70 | this.trace = new Error(); // todo: test better 71 | }; 72 | 73 | jasmine.MessageResult.prototype.toString = function() { 74 | var text = ""; 75 | for(var i = 0; i < this.values.length; i++) { 76 | if (i > 0) text += " "; 77 | if (jasmine.isString_(this.values[i])) { 78 | text += this.values[i]; 79 | } else { 80 | text += jasmine.pp(this.values[i]); 81 | } 82 | } 83 | return text; 84 | }; 85 | 86 | jasmine.ExpectationResult = function(params) { 87 | this.type = 'expect'; 88 | this.matcherName = params.matcherName; 89 | this.passed_ = params.passed; 90 | this.expected = params.expected; 91 | this.actual = params.actual; 92 | 93 | this.message = this.passed_ ? 'Passed.' : params.message; 94 | this.trace = this.passed_ ? '' : new Error(this.message); 95 | }; 96 | 97 | jasmine.ExpectationResult.prototype.toString = function () { 98 | return this.message; 99 | }; 100 | 101 | jasmine.ExpectationResult.prototype.passed = function () { 102 | return this.passed_; 103 | }; 104 | 105 | /** 106 | * Getter for the Jasmine environment. Ensures one gets created 107 | */ 108 | jasmine.getEnv = function() { 109 | return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 110 | }; 111 | 112 | /** 113 | * @ignore 114 | * @private 115 | * @param value 116 | * @returns {Boolean} 117 | */ 118 | jasmine.isArray_ = function(value) { 119 | return jasmine.isA_("Array", value); 120 | }; 121 | 122 | /** 123 | * @ignore 124 | * @private 125 | * @param value 126 | * @returns {Boolean} 127 | */ 128 | jasmine.isString_ = function(value) { 129 | return jasmine.isA_("String", value); 130 | }; 131 | 132 | /** 133 | * @ignore 134 | * @private 135 | * @param value 136 | * @returns {Boolean} 137 | */ 138 | jasmine.isNumber_ = function(value) { 139 | return jasmine.isA_("Number", value); 140 | }; 141 | 142 | /** 143 | * @ignore 144 | * @private 145 | * @param {String} typeName 146 | * @param value 147 | * @returns {Boolean} 148 | */ 149 | jasmine.isA_ = function(typeName, value) { 150 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 151 | }; 152 | 153 | /** 154 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 155 | * 156 | * @param value {Object} an object to be outputted 157 | * @returns {String} 158 | */ 159 | jasmine.pp = function(value) { 160 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 161 | stringPrettyPrinter.format(value); 162 | return stringPrettyPrinter.string; 163 | }; 164 | 165 | /** 166 | * Returns true if the object is a DOM Node. 167 | * 168 | * @param {Object} obj object to check 169 | * @returns {Boolean} 170 | */ 171 | jasmine.isDomNode = function(obj) { 172 | return obj['nodeType'] > 0; 173 | }; 174 | 175 | /** 176 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 177 | * 178 | * @example 179 | * // don't care about which function is passed in, as long as it's a function 180 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 181 | * 182 | * @param {Class} clazz 183 | * @returns matchable object of the type clazz 184 | */ 185 | jasmine.any = function(clazz) { 186 | return new jasmine.Matchers.Any(clazz); 187 | }; 188 | 189 | /** 190 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 191 | * 192 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 193 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 194 | * 195 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 196 | * 197 | * Spies are torn down at the end of every spec. 198 | * 199 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 200 | * 201 | * @example 202 | * // a stub 203 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 204 | * 205 | * // spy example 206 | * var foo = { 207 | * not: function(bool) { return !bool; } 208 | * } 209 | * 210 | * // actual foo.not will not be called, execution stops 211 | * spyOn(foo, 'not'); 212 | 213 | // foo.not spied upon, execution will continue to implementation 214 | * spyOn(foo, 'not').andCallThrough(); 215 | * 216 | * // fake example 217 | * var foo = { 218 | * not: function(bool) { return !bool; } 219 | * } 220 | * 221 | * // foo.not(val) will return val 222 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 223 | * 224 | * // mock example 225 | * foo.not(7 == 7); 226 | * expect(foo.not).toHaveBeenCalled(); 227 | * expect(foo.not).toHaveBeenCalledWith(true); 228 | * 229 | * @constructor 230 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 231 | * @param {String} name 232 | */ 233 | jasmine.Spy = function(name) { 234 | /** 235 | * The name of the spy, if provided. 236 | */ 237 | this.identity = name || 'unknown'; 238 | /** 239 | * Is this Object a spy? 240 | */ 241 | this.isSpy = true; 242 | /** 243 | * The actual function this spy stubs. 244 | */ 245 | this.plan = function() { 246 | }; 247 | /** 248 | * Tracking of the most recent call to the spy. 249 | * @example 250 | * var mySpy = jasmine.createSpy('foo'); 251 | * mySpy(1, 2); 252 | * mySpy.mostRecentCall.args = [1, 2]; 253 | */ 254 | this.mostRecentCall = {}; 255 | 256 | /** 257 | * Holds arguments for each call to the spy, indexed by call count 258 | * @example 259 | * var mySpy = jasmine.createSpy('foo'); 260 | * mySpy(1, 2); 261 | * mySpy(7, 8); 262 | * mySpy.mostRecentCall.args = [7, 8]; 263 | * mySpy.argsForCall[0] = [1, 2]; 264 | * mySpy.argsForCall[1] = [7, 8]; 265 | */ 266 | this.argsForCall = []; 267 | this.calls = []; 268 | }; 269 | 270 | /** 271 | * Tells a spy to call through to the actual implemenatation. 272 | * 273 | * @example 274 | * var foo = { 275 | * bar: function() { // do some stuff } 276 | * } 277 | * 278 | * // defining a spy on an existing property: foo.bar 279 | * spyOn(foo, 'bar').andCallThrough(); 280 | */ 281 | jasmine.Spy.prototype.andCallThrough = function() { 282 | this.plan = this.originalValue; 283 | return this; 284 | }; 285 | 286 | /** 287 | * For setting the return value of a spy. 288 | * 289 | * @example 290 | * // defining a spy from scratch: foo() returns 'baz' 291 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 292 | * 293 | * // defining a spy on an existing property: foo.bar() returns 'baz' 294 | * spyOn(foo, 'bar').andReturn('baz'); 295 | * 296 | * @param {Object} value 297 | */ 298 | jasmine.Spy.prototype.andReturn = function(value) { 299 | this.plan = function() { 300 | return value; 301 | }; 302 | return this; 303 | }; 304 | 305 | /** 306 | * For throwing an exception when a spy is called. 307 | * 308 | * @example 309 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 310 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 311 | * 312 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 313 | * spyOn(foo, 'bar').andThrow('baz'); 314 | * 315 | * @param {String} exceptionMsg 316 | */ 317 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 318 | this.plan = function() { 319 | throw exceptionMsg; 320 | }; 321 | return this; 322 | }; 323 | 324 | /** 325 | * Calls an alternate implementation when a spy is called. 326 | * 327 | * @example 328 | * var baz = function() { 329 | * // do some stuff, return something 330 | * } 331 | * // defining a spy from scratch: foo() calls the function baz 332 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 333 | * 334 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 335 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 336 | * 337 | * @param {Function} fakeFunc 338 | */ 339 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 340 | this.plan = fakeFunc; 341 | return this; 342 | }; 343 | 344 | /** 345 | * Resets all of a spy's the tracking variables so that it can be used again. 346 | * 347 | * @example 348 | * spyOn(foo, 'bar'); 349 | * 350 | * foo.bar(); 351 | * 352 | * expect(foo.bar.callCount).toEqual(1); 353 | * 354 | * foo.bar.reset(); 355 | * 356 | * expect(foo.bar.callCount).toEqual(0); 357 | */ 358 | jasmine.Spy.prototype.reset = function() { 359 | this.wasCalled = false; 360 | this.callCount = 0; 361 | this.argsForCall = []; 362 | this.calls = []; 363 | this.mostRecentCall = {}; 364 | }; 365 | 366 | jasmine.createSpy = function(name) { 367 | 368 | var spyObj = function() { 369 | spyObj.wasCalled = true; 370 | spyObj.callCount++; 371 | var args = jasmine.util.argsToArray(arguments); 372 | spyObj.mostRecentCall.object = this; 373 | spyObj.mostRecentCall.args = args; 374 | spyObj.argsForCall.push(args); 375 | spyObj.calls.push({object: this, args: args}); 376 | return spyObj.plan.apply(this, arguments); 377 | }; 378 | 379 | var spy = new jasmine.Spy(name); 380 | 381 | for (var prop in spy) { 382 | spyObj[prop] = spy[prop]; 383 | } 384 | 385 | spyObj.reset(); 386 | 387 | return spyObj; 388 | }; 389 | 390 | /** 391 | * Determines whether an object is a spy. 392 | * 393 | * @param {jasmine.Spy|Object} putativeSpy 394 | * @returns {Boolean} 395 | */ 396 | jasmine.isSpy = function(putativeSpy) { 397 | return putativeSpy && putativeSpy.isSpy; 398 | }; 399 | 400 | /** 401 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 402 | * large in one call. 403 | * 404 | * @param {String} baseName name of spy class 405 | * @param {Array} methodNames array of names of methods to make spies 406 | */ 407 | jasmine.createSpyObj = function(baseName, methodNames) { 408 | if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { 409 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 410 | } 411 | var obj = {}; 412 | for (var i = 0; i < methodNames.length; i++) { 413 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 414 | } 415 | return obj; 416 | }; 417 | 418 | /** 419 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 420 | * 421 | * Be careful not to leave calls to jasmine.log in production code. 422 | */ 423 | jasmine.log = function() { 424 | var spec = jasmine.getEnv().currentSpec; 425 | spec.log.apply(spec, arguments); 426 | }; 427 | 428 | /** 429 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 430 | * 431 | * @example 432 | * // spy example 433 | * var foo = { 434 | * not: function(bool) { return !bool; } 435 | * } 436 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 437 | * 438 | * @see jasmine.createSpy 439 | * @param obj 440 | * @param methodName 441 | * @returns a Jasmine spy that can be chained with all spy methods 442 | */ 443 | var spyOn = function(obj, methodName) { 444 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 445 | }; 446 | 447 | /** 448 | * Creates a Jasmine spec that will be added to the current suite. 449 | * 450 | * // TODO: pending tests 451 | * 452 | * @example 453 | * it('should be true', function() { 454 | * expect(true).toEqual(true); 455 | * }); 456 | * 457 | * @param {String} desc description of this specification 458 | * @param {Function} func defines the preconditions and expectations of the spec 459 | */ 460 | var it = function(desc, func) { 461 | return jasmine.getEnv().it(desc, func); 462 | }; 463 | 464 | /** 465 | * Creates a disabled Jasmine spec. 466 | * 467 | * A convenience method that allows existing specs to be disabled temporarily during development. 468 | * 469 | * @param {String} desc description of this specification 470 | * @param {Function} func defines the preconditions and expectations of the spec 471 | */ 472 | var xit = function(desc, func) { 473 | return jasmine.getEnv().xit(desc, func); 474 | }; 475 | 476 | /** 477 | * Starts a chain for a Jasmine expectation. 478 | * 479 | * It is passed an Object that is the actual value and should chain to one of the many 480 | * jasmine.Matchers functions. 481 | * 482 | * @param {Object} actual Actual value to test against and expected value 483 | */ 484 | var expect = function(actual) { 485 | return jasmine.getEnv().currentSpec.expect(actual); 486 | }; 487 | 488 | /** 489 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 490 | * 491 | * @param {Function} func Function that defines part of a jasmine spec. 492 | */ 493 | var runs = function(func) { 494 | jasmine.getEnv().currentSpec.runs(func); 495 | }; 496 | 497 | /** 498 | * Waits a fixed time period before moving to the next block. 499 | * 500 | * @deprecated Use waitsFor() instead 501 | * @param {Number} timeout milliseconds to wait 502 | */ 503 | var waits = function(timeout) { 504 | jasmine.getEnv().currentSpec.waits(timeout); 505 | }; 506 | 507 | /** 508 | * Waits for the latchFunction to return true before proceeding to the next block. 509 | * 510 | * @param {Function} latchFunction 511 | * @param {String} optional_timeoutMessage 512 | * @param {Number} optional_timeout 513 | */ 514 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 515 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 516 | }; 517 | 518 | /** 519 | * A function that is called before each spec in a suite. 520 | * 521 | * Used for spec setup, including validating assumptions. 522 | * 523 | * @param {Function} beforeEachFunction 524 | */ 525 | var beforeEach = function(beforeEachFunction) { 526 | jasmine.getEnv().beforeEach(beforeEachFunction); 527 | }; 528 | 529 | /** 530 | * A function that is called after each spec in a suite. 531 | * 532 | * Used for restoring any state that is hijacked during spec execution. 533 | * 534 | * @param {Function} afterEachFunction 535 | */ 536 | var afterEach = function(afterEachFunction) { 537 | jasmine.getEnv().afterEach(afterEachFunction); 538 | }; 539 | 540 | /** 541 | * Defines a suite of specifications. 542 | * 543 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 544 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 545 | * of setup in some tests. 546 | * 547 | * @example 548 | * // TODO: a simple suite 549 | * 550 | * // TODO: a simple suite with a nested describe block 551 | * 552 | * @param {String} description A string, usually the class under test. 553 | * @param {Function} specDefinitions function that defines several specs. 554 | */ 555 | var describe = function(description, specDefinitions) { 556 | return jasmine.getEnv().describe(description, specDefinitions); 557 | }; 558 | 559 | /** 560 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 561 | * 562 | * @param {String} description A string, usually the class under test. 563 | * @param {Function} specDefinitions function that defines several specs. 564 | */ 565 | var xdescribe = function(description, specDefinitions) { 566 | return jasmine.getEnv().xdescribe(description, specDefinitions); 567 | }; 568 | 569 | 570 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 571 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 572 | try { 573 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 574 | } catch(e) { 575 | } 576 | try { 577 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 578 | } catch(e) { 579 | } 580 | try { 581 | return new ActiveXObject("Msxml2.XMLHTTP"); 582 | } catch(e) { 583 | } 584 | try { 585 | return new ActiveXObject("Microsoft.XMLHTTP"); 586 | } catch(e) { 587 | } 588 | throw new Error("This browser does not support XMLHttpRequest."); 589 | } : XMLHttpRequest; 590 | /** 591 | * @namespace 592 | */ 593 | jasmine.util = {}; 594 | 595 | /** 596 | * Declare that a child class inherit it's prototype from the parent class. 597 | * 598 | * @private 599 | * @param {Function} childClass 600 | * @param {Function} parentClass 601 | */ 602 | jasmine.util.inherit = function(childClass, parentClass) { 603 | /** 604 | * @private 605 | */ 606 | var subclass = function() { 607 | }; 608 | subclass.prototype = parentClass.prototype; 609 | childClass.prototype = new subclass; 610 | }; 611 | 612 | jasmine.util.formatException = function(e) { 613 | var lineNumber; 614 | if (e.line) { 615 | lineNumber = e.line; 616 | } 617 | else if (e.lineNumber) { 618 | lineNumber = e.lineNumber; 619 | } 620 | 621 | var file; 622 | 623 | if (e.sourceURL) { 624 | file = e.sourceURL; 625 | } 626 | else if (e.fileName) { 627 | file = e.fileName; 628 | } 629 | 630 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 631 | 632 | if (file && lineNumber) { 633 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 634 | } 635 | 636 | return message; 637 | }; 638 | 639 | jasmine.util.htmlEscape = function(str) { 640 | if (!str) return str; 641 | return str.replace(/&/g, '&') 642 | .replace(//g, '>'); 644 | }; 645 | 646 | jasmine.util.argsToArray = function(args) { 647 | var arrayOfArgs = []; 648 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 649 | return arrayOfArgs; 650 | }; 651 | 652 | jasmine.util.extend = function(destination, source) { 653 | for (var property in source) destination[property] = source[property]; 654 | return destination; 655 | }; 656 | 657 | /** 658 | * Environment for Jasmine 659 | * 660 | * @constructor 661 | */ 662 | jasmine.Env = function() { 663 | this.currentSpec = null; 664 | this.currentSuite = null; 665 | this.currentRunner_ = new jasmine.Runner(this); 666 | 667 | this.reporter = new jasmine.MultiReporter(); 668 | 669 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 670 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 671 | this.lastUpdate = 0; 672 | this.specFilter = function() { 673 | return true; 674 | }; 675 | 676 | this.nextSpecId_ = 0; 677 | this.nextSuiteId_ = 0; 678 | this.equalityTesters_ = []; 679 | 680 | // wrap matchers 681 | this.matchersClass = function() { 682 | jasmine.Matchers.apply(this, arguments); 683 | }; 684 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 685 | 686 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 687 | }; 688 | 689 | 690 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 691 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 692 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 693 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 694 | 695 | /** 696 | * @returns an object containing jasmine version build info, if set. 697 | */ 698 | jasmine.Env.prototype.version = function () { 699 | if (jasmine.version_) { 700 | return jasmine.version_; 701 | } else { 702 | throw new Error('Version not set'); 703 | } 704 | }; 705 | 706 | /** 707 | * @returns string containing jasmine version build info, if set. 708 | */ 709 | jasmine.Env.prototype.versionString = function() { 710 | if (jasmine.version_) { 711 | var version = this.version(); 712 | return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; 713 | } else { 714 | return "version unknown"; 715 | } 716 | }; 717 | 718 | /** 719 | * @returns a sequential integer starting at 0 720 | */ 721 | jasmine.Env.prototype.nextSpecId = function () { 722 | return this.nextSpecId_++; 723 | }; 724 | 725 | /** 726 | * @returns a sequential integer starting at 0 727 | */ 728 | jasmine.Env.prototype.nextSuiteId = function () { 729 | return this.nextSuiteId_++; 730 | }; 731 | 732 | /** 733 | * Register a reporter to receive status updates from Jasmine. 734 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 735 | */ 736 | jasmine.Env.prototype.addReporter = function(reporter) { 737 | this.reporter.addReporter(reporter); 738 | }; 739 | 740 | jasmine.Env.prototype.execute = function() { 741 | this.currentRunner_.execute(); 742 | }; 743 | 744 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 745 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 746 | 747 | var parentSuite = this.currentSuite; 748 | if (parentSuite) { 749 | parentSuite.add(suite); 750 | } else { 751 | this.currentRunner_.add(suite); 752 | } 753 | 754 | this.currentSuite = suite; 755 | 756 | var declarationError = null; 757 | try { 758 | specDefinitions.call(suite); 759 | } catch(e) { 760 | declarationError = e; 761 | } 762 | 763 | this.currentSuite = parentSuite; 764 | 765 | if (declarationError) { 766 | this.it("encountered a declaration exception", function() { 767 | throw declarationError; 768 | }); 769 | } 770 | 771 | return suite; 772 | }; 773 | 774 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 775 | if (this.currentSuite) { 776 | this.currentSuite.beforeEach(beforeEachFunction); 777 | } else { 778 | this.currentRunner_.beforeEach(beforeEachFunction); 779 | } 780 | }; 781 | 782 | jasmine.Env.prototype.currentRunner = function () { 783 | return this.currentRunner_; 784 | }; 785 | 786 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 787 | if (this.currentSuite) { 788 | this.currentSuite.afterEach(afterEachFunction); 789 | } else { 790 | this.currentRunner_.afterEach(afterEachFunction); 791 | } 792 | 793 | }; 794 | 795 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 796 | return { 797 | execute: function() { 798 | } 799 | }; 800 | }; 801 | 802 | jasmine.Env.prototype.it = function(description, func) { 803 | var spec = new jasmine.Spec(this, this.currentSuite, description); 804 | this.currentSuite.add(spec); 805 | this.currentSpec = spec; 806 | 807 | if (func) { 808 | spec.runs(func); 809 | } 810 | 811 | return spec; 812 | }; 813 | 814 | jasmine.Env.prototype.xit = function(desc, func) { 815 | return { 816 | id: this.nextSpecId(), 817 | runs: function() { 818 | } 819 | }; 820 | }; 821 | 822 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 823 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 824 | return true; 825 | } 826 | 827 | a.__Jasmine_been_here_before__ = b; 828 | b.__Jasmine_been_here_before__ = a; 829 | 830 | var hasKey = function(obj, keyName) { 831 | return obj != null && obj[keyName] !== jasmine.undefined; 832 | }; 833 | 834 | for (var property in b) { 835 | if (!hasKey(a, property) && hasKey(b, property)) { 836 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 837 | } 838 | } 839 | for (property in a) { 840 | if (!hasKey(b, property) && hasKey(a, property)) { 841 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 842 | } 843 | } 844 | for (property in b) { 845 | if (property == '__Jasmine_been_here_before__') continue; 846 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 847 | 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."); 848 | } 849 | } 850 | 851 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 852 | mismatchValues.push("arrays were not the same length"); 853 | } 854 | 855 | delete a.__Jasmine_been_here_before__; 856 | delete b.__Jasmine_been_here_before__; 857 | return (mismatchKeys.length == 0 && mismatchValues.length == 0); 858 | }; 859 | 860 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 861 | mismatchKeys = mismatchKeys || []; 862 | mismatchValues = mismatchValues || []; 863 | 864 | for (var i = 0; i < this.equalityTesters_.length; i++) { 865 | var equalityTester = this.equalityTesters_[i]; 866 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 867 | if (result !== jasmine.undefined) return result; 868 | } 869 | 870 | if (a === b) return true; 871 | 872 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 873 | return (a == jasmine.undefined && b == jasmine.undefined); 874 | } 875 | 876 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 877 | return a === b; 878 | } 879 | 880 | if (a instanceof Date && b instanceof Date) { 881 | return a.getTime() == b.getTime(); 882 | } 883 | 884 | if (a instanceof jasmine.Matchers.Any) { 885 | return a.matches(b); 886 | } 887 | 888 | if (b instanceof jasmine.Matchers.Any) { 889 | return b.matches(a); 890 | } 891 | 892 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 893 | return (a == b); 894 | } 895 | 896 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 897 | return (a == b); 898 | } 899 | 900 | if (typeof a === "object" && typeof b === "object") { 901 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 902 | } 903 | 904 | //Straight check 905 | return (a === b); 906 | }; 907 | 908 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 909 | if (jasmine.isArray_(haystack)) { 910 | for (var i = 0; i < haystack.length; i++) { 911 | if (this.equals_(haystack[i], needle)) return true; 912 | } 913 | return false; 914 | } 915 | return haystack.indexOf(needle) >= 0; 916 | }; 917 | 918 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 919 | this.equalityTesters_.push(equalityTester); 920 | }; 921 | /** No-op base class for Jasmine reporters. 922 | * 923 | * @constructor 924 | */ 925 | jasmine.Reporter = function() { 926 | }; 927 | 928 | //noinspection JSUnusedLocalSymbols 929 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 930 | }; 931 | 932 | //noinspection JSUnusedLocalSymbols 933 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 934 | }; 935 | 936 | //noinspection JSUnusedLocalSymbols 937 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 938 | }; 939 | 940 | //noinspection JSUnusedLocalSymbols 941 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 942 | }; 943 | 944 | //noinspection JSUnusedLocalSymbols 945 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 946 | }; 947 | 948 | //noinspection JSUnusedLocalSymbols 949 | jasmine.Reporter.prototype.log = function(str) { 950 | }; 951 | 952 | /** 953 | * Blocks are functions with executable code that make up a spec. 954 | * 955 | * @constructor 956 | * @param {jasmine.Env} env 957 | * @param {Function} func 958 | * @param {jasmine.Spec} spec 959 | */ 960 | jasmine.Block = function(env, func, spec) { 961 | this.env = env; 962 | this.func = func; 963 | this.spec = spec; 964 | }; 965 | 966 | jasmine.Block.prototype.execute = function(onComplete) { 967 | try { 968 | this.func.apply(this.spec); 969 | } catch (e) { 970 | this.spec.fail(e); 971 | } 972 | onComplete(); 973 | }; 974 | /** JavaScript API reporter. 975 | * 976 | * @constructor 977 | */ 978 | jasmine.JsApiReporter = function() { 979 | this.started = false; 980 | this.finished = false; 981 | this.suites_ = []; 982 | this.results_ = {}; 983 | }; 984 | 985 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 986 | this.started = true; 987 | var suites = runner.topLevelSuites(); 988 | for (var i = 0; i < suites.length; i++) { 989 | var suite = suites[i]; 990 | this.suites_.push(this.summarize_(suite)); 991 | } 992 | }; 993 | 994 | jasmine.JsApiReporter.prototype.suites = function() { 995 | return this.suites_; 996 | }; 997 | 998 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 999 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1000 | var summary = { 1001 | id: suiteOrSpec.id, 1002 | name: suiteOrSpec.description, 1003 | type: isSuite ? 'suite' : 'spec', 1004 | children: [] 1005 | }; 1006 | 1007 | if (isSuite) { 1008 | var children = suiteOrSpec.children(); 1009 | for (var i = 0; i < children.length; i++) { 1010 | summary.children.push(this.summarize_(children[i])); 1011 | } 1012 | } 1013 | return summary; 1014 | }; 1015 | 1016 | jasmine.JsApiReporter.prototype.results = function() { 1017 | return this.results_; 1018 | }; 1019 | 1020 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1021 | return this.results_[specId]; 1022 | }; 1023 | 1024 | //noinspection JSUnusedLocalSymbols 1025 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1026 | this.finished = true; 1027 | }; 1028 | 1029 | //noinspection JSUnusedLocalSymbols 1030 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1031 | }; 1032 | 1033 | //noinspection JSUnusedLocalSymbols 1034 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1035 | this.results_[spec.id] = { 1036 | messages: spec.results().getItems(), 1037 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1038 | }; 1039 | }; 1040 | 1041 | //noinspection JSUnusedLocalSymbols 1042 | jasmine.JsApiReporter.prototype.log = function(str) { 1043 | }; 1044 | 1045 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1046 | var results = {}; 1047 | for (var i = 0; i < specIds.length; i++) { 1048 | var specId = specIds[i]; 1049 | results[specId] = this.summarizeResult_(this.results_[specId]); 1050 | } 1051 | return results; 1052 | }; 1053 | 1054 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1055 | var summaryMessages = []; 1056 | var messagesLength = result.messages.length; 1057 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1058 | var resultMessage = result.messages[messageIndex]; 1059 | summaryMessages.push({ 1060 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1061 | passed: resultMessage.passed ? resultMessage.passed() : true, 1062 | type: resultMessage.type, 1063 | message: resultMessage.message, 1064 | trace: { 1065 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1066 | } 1067 | }); 1068 | } 1069 | 1070 | return { 1071 | result : result.result, 1072 | messages : summaryMessages 1073 | }; 1074 | }; 1075 | 1076 | /** 1077 | * @constructor 1078 | * @param {jasmine.Env} env 1079 | * @param actual 1080 | * @param {jasmine.Spec} spec 1081 | */ 1082 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1083 | this.env = env; 1084 | this.actual = actual; 1085 | this.spec = spec; 1086 | this.isNot = opt_isNot || false; 1087 | this.reportWasCalled_ = false; 1088 | }; 1089 | 1090 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1091 | jasmine.Matchers.pp = function(str) { 1092 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1093 | }; 1094 | 1095 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1096 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1097 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1098 | }; 1099 | 1100 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1101 | for (var methodName in prototype) { 1102 | if (methodName == 'report') continue; 1103 | var orig = prototype[methodName]; 1104 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1105 | } 1106 | }; 1107 | 1108 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1109 | return function() { 1110 | var matcherArgs = jasmine.util.argsToArray(arguments); 1111 | var result = matcherFunction.apply(this, arguments); 1112 | 1113 | if (this.isNot) { 1114 | result = !result; 1115 | } 1116 | 1117 | if (this.reportWasCalled_) return result; 1118 | 1119 | var message; 1120 | if (!result) { 1121 | if (this.message) { 1122 | message = this.message.apply(this, arguments); 1123 | if (jasmine.isArray_(message)) { 1124 | message = message[this.isNot ? 1 : 0]; 1125 | } 1126 | } else { 1127 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1128 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1129 | if (matcherArgs.length > 0) { 1130 | for (var i = 0; i < matcherArgs.length; i++) { 1131 | if (i > 0) message += ","; 1132 | message += " " + jasmine.pp(matcherArgs[i]); 1133 | } 1134 | } 1135 | message += "."; 1136 | } 1137 | } 1138 | var expectationResult = new jasmine.ExpectationResult({ 1139 | matcherName: matcherName, 1140 | passed: result, 1141 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1142 | actual: this.actual, 1143 | message: message 1144 | }); 1145 | this.spec.addMatcherResult(expectationResult); 1146 | return jasmine.undefined; 1147 | }; 1148 | }; 1149 | 1150 | 1151 | 1152 | 1153 | /** 1154 | * toBe: compares the actual to the expected using === 1155 | * @param expected 1156 | */ 1157 | jasmine.Matchers.prototype.toBe = function(expected) { 1158 | return this.actual === expected; 1159 | }; 1160 | 1161 | /** 1162 | * toNotBe: compares the actual to the expected using !== 1163 | * @param expected 1164 | * @deprecated as of 1.0. Use not.toBe() instead. 1165 | */ 1166 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1167 | return this.actual !== expected; 1168 | }; 1169 | 1170 | /** 1171 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1172 | * 1173 | * @param expected 1174 | */ 1175 | jasmine.Matchers.prototype.toEqual = function(expected) { 1176 | return this.env.equals_(this.actual, expected); 1177 | }; 1178 | 1179 | /** 1180 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1181 | * @param expected 1182 | * @deprecated as of 1.0. Use not.toNotEqual() instead. 1183 | */ 1184 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1185 | return !this.env.equals_(this.actual, expected); 1186 | }; 1187 | 1188 | /** 1189 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1190 | * a pattern or a String. 1191 | * 1192 | * @param expected 1193 | */ 1194 | jasmine.Matchers.prototype.toMatch = function(expected) { 1195 | return new RegExp(expected).test(this.actual); 1196 | }; 1197 | 1198 | /** 1199 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1200 | * @param expected 1201 | * @deprecated as of 1.0. Use not.toMatch() instead. 1202 | */ 1203 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1204 | return !(new RegExp(expected).test(this.actual)); 1205 | }; 1206 | 1207 | /** 1208 | * Matcher that compares the actual to jasmine.undefined. 1209 | */ 1210 | jasmine.Matchers.prototype.toBeDefined = function() { 1211 | return (this.actual !== jasmine.undefined); 1212 | }; 1213 | 1214 | /** 1215 | * Matcher that compares the actual to jasmine.undefined. 1216 | */ 1217 | jasmine.Matchers.prototype.toBeUndefined = function() { 1218 | return (this.actual === jasmine.undefined); 1219 | }; 1220 | 1221 | /** 1222 | * Matcher that compares the actual to null. 1223 | */ 1224 | jasmine.Matchers.prototype.toBeNull = function() { 1225 | return (this.actual === null); 1226 | }; 1227 | 1228 | /** 1229 | * Matcher that boolean not-nots the actual. 1230 | */ 1231 | jasmine.Matchers.prototype.toBeTruthy = function() { 1232 | return !!this.actual; 1233 | }; 1234 | 1235 | 1236 | /** 1237 | * Matcher that boolean nots the actual. 1238 | */ 1239 | jasmine.Matchers.prototype.toBeFalsy = function() { 1240 | return !this.actual; 1241 | }; 1242 | 1243 | 1244 | /** 1245 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1246 | */ 1247 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1248 | if (arguments.length > 0) { 1249 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1250 | } 1251 | 1252 | if (!jasmine.isSpy(this.actual)) { 1253 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1254 | } 1255 | 1256 | this.message = function() { 1257 | return [ 1258 | "Expected spy " + this.actual.identity + " to have been called.", 1259 | "Expected spy " + this.actual.identity + " not to have been called." 1260 | ]; 1261 | }; 1262 | 1263 | return this.actual.wasCalled; 1264 | }; 1265 | 1266 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1267 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1268 | 1269 | /** 1270 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1271 | * 1272 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1273 | */ 1274 | jasmine.Matchers.prototype.wasNotCalled = function() { 1275 | if (arguments.length > 0) { 1276 | throw new Error('wasNotCalled does not take arguments'); 1277 | } 1278 | 1279 | if (!jasmine.isSpy(this.actual)) { 1280 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1281 | } 1282 | 1283 | this.message = function() { 1284 | return [ 1285 | "Expected spy " + this.actual.identity + " to not have been called.", 1286 | "Expected spy " + this.actual.identity + " to have been called." 1287 | ]; 1288 | }; 1289 | 1290 | return !this.actual.wasCalled; 1291 | }; 1292 | 1293 | /** 1294 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1295 | * 1296 | * @example 1297 | * 1298 | */ 1299 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1300 | var expectedArgs = jasmine.util.argsToArray(arguments); 1301 | if (!jasmine.isSpy(this.actual)) { 1302 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1303 | } 1304 | this.message = function() { 1305 | if (this.actual.callCount == 0) { 1306 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1307 | return [ 1308 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1309 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1310 | ]; 1311 | } else { 1312 | return [ 1313 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1314 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1315 | ]; 1316 | } 1317 | }; 1318 | 1319 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1320 | }; 1321 | 1322 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1323 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1324 | 1325 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1326 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1327 | var expectedArgs = jasmine.util.argsToArray(arguments); 1328 | if (!jasmine.isSpy(this.actual)) { 1329 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1330 | } 1331 | 1332 | this.message = function() { 1333 | return [ 1334 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1335 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1336 | ] 1337 | }; 1338 | 1339 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1340 | }; 1341 | 1342 | /** 1343 | * Matcher that checks that the expected item is an element in the actual Array. 1344 | * 1345 | * @param {Object} expected 1346 | */ 1347 | jasmine.Matchers.prototype.toContain = function(expected) { 1348 | return this.env.contains_(this.actual, expected); 1349 | }; 1350 | 1351 | /** 1352 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1353 | * 1354 | * @param {Object} expected 1355 | * @deprecated as of 1.0. Use not.toNotContain() instead. 1356 | */ 1357 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1358 | return !this.env.contains_(this.actual, expected); 1359 | }; 1360 | 1361 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1362 | return this.actual < expected; 1363 | }; 1364 | 1365 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1366 | return this.actual > expected; 1367 | }; 1368 | 1369 | /** 1370 | * Matcher that checks that the expected exception was thrown by the actual. 1371 | * 1372 | * @param {String} expected 1373 | */ 1374 | jasmine.Matchers.prototype.toThrow = function(expected) { 1375 | var result = false; 1376 | var exception; 1377 | if (typeof this.actual != 'function') { 1378 | throw new Error('Actual is not a function'); 1379 | } 1380 | try { 1381 | this.actual(); 1382 | } catch (e) { 1383 | exception = e; 1384 | } 1385 | if (exception) { 1386 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1387 | } 1388 | 1389 | var not = this.isNot ? "not " : ""; 1390 | 1391 | this.message = function() { 1392 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1393 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' '); 1394 | } else { 1395 | return "Expected function to throw an exception."; 1396 | } 1397 | }; 1398 | 1399 | return result; 1400 | }; 1401 | 1402 | jasmine.Matchers.Any = function(expectedClass) { 1403 | this.expectedClass = expectedClass; 1404 | }; 1405 | 1406 | jasmine.Matchers.Any.prototype.matches = function(other) { 1407 | if (this.expectedClass == String) { 1408 | return typeof other == 'string' || other instanceof String; 1409 | } 1410 | 1411 | if (this.expectedClass == Number) { 1412 | return typeof other == 'number' || other instanceof Number; 1413 | } 1414 | 1415 | if (this.expectedClass == Function) { 1416 | return typeof other == 'function' || other instanceof Function; 1417 | } 1418 | 1419 | if (this.expectedClass == Object) { 1420 | return typeof other == 'object'; 1421 | } 1422 | 1423 | return other instanceof this.expectedClass; 1424 | }; 1425 | 1426 | jasmine.Matchers.Any.prototype.toString = function() { 1427 | return ''; 1428 | }; 1429 | 1430 | /** 1431 | * @constructor 1432 | */ 1433 | jasmine.MultiReporter = function() { 1434 | this.subReporters_ = []; 1435 | }; 1436 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1437 | 1438 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1439 | this.subReporters_.push(reporter); 1440 | }; 1441 | 1442 | (function() { 1443 | var functionNames = [ 1444 | "reportRunnerStarting", 1445 | "reportRunnerResults", 1446 | "reportSuiteResults", 1447 | "reportSpecStarting", 1448 | "reportSpecResults", 1449 | "log" 1450 | ]; 1451 | for (var i = 0; i < functionNames.length; i++) { 1452 | var functionName = functionNames[i]; 1453 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1454 | return function() { 1455 | for (var j = 0; j < this.subReporters_.length; j++) { 1456 | var subReporter = this.subReporters_[j]; 1457 | if (subReporter[functionName]) { 1458 | subReporter[functionName].apply(subReporter, arguments); 1459 | } 1460 | } 1461 | }; 1462 | })(functionName); 1463 | } 1464 | })(); 1465 | /** 1466 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1467 | * 1468 | * @constructor 1469 | */ 1470 | jasmine.NestedResults = function() { 1471 | /** 1472 | * The total count of results 1473 | */ 1474 | this.totalCount = 0; 1475 | /** 1476 | * Number of passed results 1477 | */ 1478 | this.passedCount = 0; 1479 | /** 1480 | * Number of failed results 1481 | */ 1482 | this.failedCount = 0; 1483 | /** 1484 | * Was this suite/spec skipped? 1485 | */ 1486 | this.skipped = false; 1487 | /** 1488 | * @ignore 1489 | */ 1490 | this.items_ = []; 1491 | }; 1492 | 1493 | /** 1494 | * Roll up the result counts. 1495 | * 1496 | * @param result 1497 | */ 1498 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1499 | this.totalCount += result.totalCount; 1500 | this.passedCount += result.passedCount; 1501 | this.failedCount += result.failedCount; 1502 | }; 1503 | 1504 | /** 1505 | * Adds a log message. 1506 | * @param values Array of message parts which will be concatenated later. 1507 | */ 1508 | jasmine.NestedResults.prototype.log = function(values) { 1509 | this.items_.push(new jasmine.MessageResult(values)); 1510 | }; 1511 | 1512 | /** 1513 | * Getter for the results: message & results. 1514 | */ 1515 | jasmine.NestedResults.prototype.getItems = function() { 1516 | return this.items_; 1517 | }; 1518 | 1519 | /** 1520 | * Adds a result, tracking counts (total, passed, & failed) 1521 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1522 | */ 1523 | jasmine.NestedResults.prototype.addResult = function(result) { 1524 | if (result.type != 'log') { 1525 | if (result.items_) { 1526 | this.rollupCounts(result); 1527 | } else { 1528 | this.totalCount++; 1529 | if (result.passed()) { 1530 | this.passedCount++; 1531 | } else { 1532 | this.failedCount++; 1533 | } 1534 | } 1535 | } 1536 | this.items_.push(result); 1537 | }; 1538 | 1539 | /** 1540 | * @returns {Boolean} True if everything below passed 1541 | */ 1542 | jasmine.NestedResults.prototype.passed = function() { 1543 | return this.passedCount === this.totalCount; 1544 | }; 1545 | /** 1546 | * Base class for pretty printing for expectation results. 1547 | */ 1548 | jasmine.PrettyPrinter = function() { 1549 | this.ppNestLevel_ = 0; 1550 | }; 1551 | 1552 | /** 1553 | * Formats a value in a nice, human-readable string. 1554 | * 1555 | * @param value 1556 | */ 1557 | jasmine.PrettyPrinter.prototype.format = function(value) { 1558 | if (this.ppNestLevel_ > 40) { 1559 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1560 | } 1561 | 1562 | this.ppNestLevel_++; 1563 | try { 1564 | if (value === jasmine.undefined) { 1565 | this.emitScalar('undefined'); 1566 | } else if (value === null) { 1567 | this.emitScalar('null'); 1568 | } else if (value === jasmine.getGlobal()) { 1569 | this.emitScalar(''); 1570 | } else if (value instanceof jasmine.Matchers.Any) { 1571 | this.emitScalar(value.toString()); 1572 | } else if (typeof value === 'string') { 1573 | this.emitString(value); 1574 | } else if (jasmine.isSpy(value)) { 1575 | this.emitScalar("spy on " + value.identity); 1576 | } else if (value instanceof RegExp) { 1577 | this.emitScalar(value.toString()); 1578 | } else if (typeof value === 'function') { 1579 | this.emitScalar('Function'); 1580 | } else if (typeof value.nodeType === 'number') { 1581 | this.emitScalar('HTMLNode'); 1582 | } else if (value instanceof Date) { 1583 | this.emitScalar('Date(' + value + ')'); 1584 | } else if (value.__Jasmine_been_here_before__) { 1585 | this.emitScalar(''); 1586 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1587 | value.__Jasmine_been_here_before__ = true; 1588 | if (jasmine.isArray_(value)) { 1589 | this.emitArray(value); 1590 | } else { 1591 | this.emitObject(value); 1592 | } 1593 | delete value.__Jasmine_been_here_before__; 1594 | } else { 1595 | this.emitScalar(value.toString()); 1596 | } 1597 | } finally { 1598 | this.ppNestLevel_--; 1599 | } 1600 | }; 1601 | 1602 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1603 | for (var property in obj) { 1604 | if (property == '__Jasmine_been_here_before__') continue; 1605 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false); 1606 | } 1607 | }; 1608 | 1609 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1610 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1611 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1612 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1613 | 1614 | jasmine.StringPrettyPrinter = function() { 1615 | jasmine.PrettyPrinter.call(this); 1616 | 1617 | this.string = ''; 1618 | }; 1619 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1620 | 1621 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1622 | this.append(value); 1623 | }; 1624 | 1625 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1626 | this.append("'" + value + "'"); 1627 | }; 1628 | 1629 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1630 | this.append('[ '); 1631 | for (var i = 0; i < array.length; i++) { 1632 | if (i > 0) { 1633 | this.append(', '); 1634 | } 1635 | this.format(array[i]); 1636 | } 1637 | this.append(' ]'); 1638 | }; 1639 | 1640 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1641 | var self = this; 1642 | this.append('{ '); 1643 | var first = true; 1644 | 1645 | this.iterateObject(obj, function(property, isGetter) { 1646 | if (first) { 1647 | first = false; 1648 | } else { 1649 | self.append(', '); 1650 | } 1651 | 1652 | self.append(property); 1653 | self.append(' : '); 1654 | if (isGetter) { 1655 | self.append(''); 1656 | } else { 1657 | self.format(obj[property]); 1658 | } 1659 | }); 1660 | 1661 | this.append(' }'); 1662 | }; 1663 | 1664 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1665 | this.string += value; 1666 | }; 1667 | jasmine.Queue = function(env) { 1668 | this.env = env; 1669 | this.blocks = []; 1670 | this.running = false; 1671 | this.index = 0; 1672 | this.offset = 0; 1673 | this.abort = false; 1674 | }; 1675 | 1676 | jasmine.Queue.prototype.addBefore = function(block) { 1677 | this.blocks.unshift(block); 1678 | }; 1679 | 1680 | jasmine.Queue.prototype.add = function(block) { 1681 | this.blocks.push(block); 1682 | }; 1683 | 1684 | jasmine.Queue.prototype.insertNext = function(block) { 1685 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1686 | this.offset++; 1687 | }; 1688 | 1689 | jasmine.Queue.prototype.start = function(onComplete) { 1690 | this.running = true; 1691 | this.onComplete = onComplete; 1692 | this.next_(); 1693 | }; 1694 | 1695 | jasmine.Queue.prototype.isRunning = function() { 1696 | return this.running; 1697 | }; 1698 | 1699 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1700 | 1701 | jasmine.Queue.prototype.next_ = function() { 1702 | var self = this; 1703 | var goAgain = true; 1704 | 1705 | while (goAgain) { 1706 | goAgain = false; 1707 | 1708 | if (self.index < self.blocks.length && !this.abort) { 1709 | var calledSynchronously = true; 1710 | var completedSynchronously = false; 1711 | 1712 | var onComplete = function () { 1713 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1714 | completedSynchronously = true; 1715 | return; 1716 | } 1717 | 1718 | if (self.blocks[self.index].abort) { 1719 | self.abort = true; 1720 | } 1721 | 1722 | self.offset = 0; 1723 | self.index++; 1724 | 1725 | var now = new Date().getTime(); 1726 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1727 | self.env.lastUpdate = now; 1728 | self.env.setTimeout(function() { 1729 | self.next_(); 1730 | }, 0); 1731 | } else { 1732 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1733 | goAgain = true; 1734 | } else { 1735 | self.next_(); 1736 | } 1737 | } 1738 | }; 1739 | self.blocks[self.index].execute(onComplete); 1740 | 1741 | calledSynchronously = false; 1742 | if (completedSynchronously) { 1743 | onComplete(); 1744 | } 1745 | 1746 | } else { 1747 | self.running = false; 1748 | if (self.onComplete) { 1749 | self.onComplete(); 1750 | } 1751 | } 1752 | } 1753 | }; 1754 | 1755 | jasmine.Queue.prototype.results = function() { 1756 | var results = new jasmine.NestedResults(); 1757 | for (var i = 0; i < this.blocks.length; i++) { 1758 | if (this.blocks[i].results) { 1759 | results.addResult(this.blocks[i].results()); 1760 | } 1761 | } 1762 | return results; 1763 | }; 1764 | 1765 | 1766 | /** 1767 | * Runner 1768 | * 1769 | * @constructor 1770 | * @param {jasmine.Env} env 1771 | */ 1772 | jasmine.Runner = function(env) { 1773 | var self = this; 1774 | self.env = env; 1775 | self.queue = new jasmine.Queue(env); 1776 | self.before_ = []; 1777 | self.after_ = []; 1778 | self.suites_ = []; 1779 | }; 1780 | 1781 | jasmine.Runner.prototype.execute = function() { 1782 | var self = this; 1783 | if (self.env.reporter.reportRunnerStarting) { 1784 | self.env.reporter.reportRunnerStarting(this); 1785 | } 1786 | self.queue.start(function () { 1787 | self.finishCallback(); 1788 | }); 1789 | }; 1790 | 1791 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1792 | beforeEachFunction.typeName = 'beforeEach'; 1793 | this.before_.splice(0,0,beforeEachFunction); 1794 | }; 1795 | 1796 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1797 | afterEachFunction.typeName = 'afterEach'; 1798 | this.after_.splice(0,0,afterEachFunction); 1799 | }; 1800 | 1801 | 1802 | jasmine.Runner.prototype.finishCallback = function() { 1803 | this.env.reporter.reportRunnerResults(this); 1804 | }; 1805 | 1806 | jasmine.Runner.prototype.addSuite = function(suite) { 1807 | this.suites_.push(suite); 1808 | }; 1809 | 1810 | jasmine.Runner.prototype.add = function(block) { 1811 | if (block instanceof jasmine.Suite) { 1812 | this.addSuite(block); 1813 | } 1814 | this.queue.add(block); 1815 | }; 1816 | 1817 | jasmine.Runner.prototype.specs = function () { 1818 | var suites = this.suites(); 1819 | var specs = []; 1820 | for (var i = 0; i < suites.length; i++) { 1821 | specs = specs.concat(suites[i].specs()); 1822 | } 1823 | return specs; 1824 | }; 1825 | 1826 | jasmine.Runner.prototype.suites = function() { 1827 | return this.suites_; 1828 | }; 1829 | 1830 | jasmine.Runner.prototype.topLevelSuites = function() { 1831 | var topLevelSuites = []; 1832 | for (var i = 0; i < this.suites_.length; i++) { 1833 | if (!this.suites_[i].parentSuite) { 1834 | topLevelSuites.push(this.suites_[i]); 1835 | } 1836 | } 1837 | return topLevelSuites; 1838 | }; 1839 | 1840 | jasmine.Runner.prototype.results = function() { 1841 | return this.queue.results(); 1842 | }; 1843 | /** 1844 | * Internal representation of a Jasmine specification, or test. 1845 | * 1846 | * @constructor 1847 | * @param {jasmine.Env} env 1848 | * @param {jasmine.Suite} suite 1849 | * @param {String} description 1850 | */ 1851 | jasmine.Spec = function(env, suite, description) { 1852 | if (!env) { 1853 | throw new Error('jasmine.Env() required'); 1854 | } 1855 | if (!suite) { 1856 | throw new Error('jasmine.Suite() required'); 1857 | } 1858 | var spec = this; 1859 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 1860 | spec.env = env; 1861 | spec.suite = suite; 1862 | spec.description = description; 1863 | spec.queue = new jasmine.Queue(env); 1864 | 1865 | spec.afterCallbacks = []; 1866 | spec.spies_ = []; 1867 | 1868 | spec.results_ = new jasmine.NestedResults(); 1869 | spec.results_.description = description; 1870 | spec.matchersClass = null; 1871 | }; 1872 | 1873 | jasmine.Spec.prototype.getFullName = function() { 1874 | return this.suite.getFullName() + ' ' + this.description + '.'; 1875 | }; 1876 | 1877 | 1878 | jasmine.Spec.prototype.results = function() { 1879 | return this.results_; 1880 | }; 1881 | 1882 | /** 1883 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 1884 | * 1885 | * Be careful not to leave calls to jasmine.log in production code. 1886 | */ 1887 | jasmine.Spec.prototype.log = function() { 1888 | return this.results_.log(arguments); 1889 | }; 1890 | 1891 | jasmine.Spec.prototype.runs = function (func) { 1892 | var block = new jasmine.Block(this.env, func, this); 1893 | this.addToQueue(block); 1894 | return this; 1895 | }; 1896 | 1897 | jasmine.Spec.prototype.addToQueue = function (block) { 1898 | if (this.queue.isRunning()) { 1899 | this.queue.insertNext(block); 1900 | } else { 1901 | this.queue.add(block); 1902 | } 1903 | }; 1904 | 1905 | /** 1906 | * @param {jasmine.ExpectationResult} result 1907 | */ 1908 | jasmine.Spec.prototype.addMatcherResult = function(result) { 1909 | this.results_.addResult(result); 1910 | }; 1911 | 1912 | jasmine.Spec.prototype.expect = function(actual) { 1913 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 1914 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 1915 | return positive; 1916 | }; 1917 | 1918 | /** 1919 | * Waits a fixed time period before moving to the next block. 1920 | * 1921 | * @deprecated Use waitsFor() instead 1922 | * @param {Number} timeout milliseconds to wait 1923 | */ 1924 | jasmine.Spec.prototype.waits = function(timeout) { 1925 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1926 | this.addToQueue(waitsFunc); 1927 | return this; 1928 | }; 1929 | 1930 | /** 1931 | * Waits for the latchFunction to return true before proceeding to the next block. 1932 | * 1933 | * @param {Function} latchFunction 1934 | * @param {String} optional_timeoutMessage 1935 | * @param {Number} optional_timeout 1936 | */ 1937 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 1938 | var latchFunction_ = null; 1939 | var optional_timeoutMessage_ = null; 1940 | var optional_timeout_ = null; 1941 | 1942 | for (var i = 0; i < arguments.length; i++) { 1943 | var arg = arguments[i]; 1944 | switch (typeof arg) { 1945 | case 'function': 1946 | latchFunction_ = arg; 1947 | break; 1948 | case 'string': 1949 | optional_timeoutMessage_ = arg; 1950 | break; 1951 | case 'number': 1952 | optional_timeout_ = arg; 1953 | break; 1954 | } 1955 | } 1956 | 1957 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 1958 | this.addToQueue(waitsForFunc); 1959 | return this; 1960 | }; 1961 | 1962 | jasmine.Spec.prototype.fail = function (e) { 1963 | var expectationResult = new jasmine.ExpectationResult({ 1964 | passed: false, 1965 | message: e ? jasmine.util.formatException(e) : 'Exception' 1966 | }); 1967 | this.results_.addResult(expectationResult); 1968 | }; 1969 | 1970 | jasmine.Spec.prototype.getMatchersClass_ = function() { 1971 | return this.matchersClass || this.env.matchersClass; 1972 | }; 1973 | 1974 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 1975 | var parent = this.getMatchersClass_(); 1976 | var newMatchersClass = function() { 1977 | parent.apply(this, arguments); 1978 | }; 1979 | jasmine.util.inherit(newMatchersClass, parent); 1980 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 1981 | this.matchersClass = newMatchersClass; 1982 | }; 1983 | 1984 | jasmine.Spec.prototype.finishCallback = function() { 1985 | this.env.reporter.reportSpecResults(this); 1986 | }; 1987 | 1988 | jasmine.Spec.prototype.finish = function(onComplete) { 1989 | this.removeAllSpies(); 1990 | this.finishCallback(); 1991 | if (onComplete) { 1992 | onComplete(); 1993 | } 1994 | }; 1995 | 1996 | jasmine.Spec.prototype.after = function(doAfter) { 1997 | if (this.queue.isRunning()) { 1998 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 1999 | } else { 2000 | this.afterCallbacks.unshift(doAfter); 2001 | } 2002 | }; 2003 | 2004 | jasmine.Spec.prototype.execute = function(onComplete) { 2005 | var spec = this; 2006 | if (!spec.env.specFilter(spec)) { 2007 | spec.results_.skipped = true; 2008 | spec.finish(onComplete); 2009 | return; 2010 | } 2011 | 2012 | this.env.reporter.reportSpecStarting(this); 2013 | 2014 | spec.env.currentSpec = spec; 2015 | 2016 | spec.addBeforesAndAftersToQueue(); 2017 | 2018 | spec.queue.start(function () { 2019 | spec.finish(onComplete); 2020 | }); 2021 | }; 2022 | 2023 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2024 | var runner = this.env.currentRunner(); 2025 | var i; 2026 | 2027 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2028 | for (i = 0; i < suite.before_.length; i++) { 2029 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2030 | } 2031 | } 2032 | for (i = 0; i < runner.before_.length; i++) { 2033 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2034 | } 2035 | for (i = 0; i < this.afterCallbacks.length; i++) { 2036 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2037 | } 2038 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2039 | for (i = 0; i < suite.after_.length; i++) { 2040 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2041 | } 2042 | } 2043 | for (i = 0; i < runner.after_.length; i++) { 2044 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2045 | } 2046 | }; 2047 | 2048 | jasmine.Spec.prototype.explodes = function() { 2049 | throw 'explodes function should not have been called'; 2050 | }; 2051 | 2052 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2053 | if (obj == jasmine.undefined) { 2054 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2055 | } 2056 | 2057 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2058 | throw methodName + '() method does not exist'; 2059 | } 2060 | 2061 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2062 | throw new Error(methodName + ' has already been spied upon'); 2063 | } 2064 | 2065 | var spyObj = jasmine.createSpy(methodName); 2066 | 2067 | this.spies_.push(spyObj); 2068 | spyObj.baseObj = obj; 2069 | spyObj.methodName = methodName; 2070 | spyObj.originalValue = obj[methodName]; 2071 | 2072 | obj[methodName] = spyObj; 2073 | 2074 | return spyObj; 2075 | }; 2076 | 2077 | jasmine.Spec.prototype.removeAllSpies = function() { 2078 | for (var i = 0; i < this.spies_.length; i++) { 2079 | var spy = this.spies_[i]; 2080 | spy.baseObj[spy.methodName] = spy.originalValue; 2081 | } 2082 | this.spies_ = []; 2083 | }; 2084 | 2085 | /** 2086 | * Internal representation of a Jasmine suite. 2087 | * 2088 | * @constructor 2089 | * @param {jasmine.Env} env 2090 | * @param {String} description 2091 | * @param {Function} specDefinitions 2092 | * @param {jasmine.Suite} parentSuite 2093 | */ 2094 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2095 | var self = this; 2096 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2097 | self.description = description; 2098 | self.queue = new jasmine.Queue(env); 2099 | self.parentSuite = parentSuite; 2100 | self.env = env; 2101 | self.before_ = []; 2102 | self.after_ = []; 2103 | self.children_ = []; 2104 | self.suites_ = []; 2105 | self.specs_ = []; 2106 | }; 2107 | 2108 | jasmine.Suite.prototype.getFullName = function() { 2109 | var fullName = this.description; 2110 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2111 | fullName = parentSuite.description + ' ' + fullName; 2112 | } 2113 | return fullName; 2114 | }; 2115 | 2116 | jasmine.Suite.prototype.finish = function(onComplete) { 2117 | this.env.reporter.reportSuiteResults(this); 2118 | this.finished = true; 2119 | if (typeof(onComplete) == 'function') { 2120 | onComplete(); 2121 | } 2122 | }; 2123 | 2124 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2125 | beforeEachFunction.typeName = 'beforeEach'; 2126 | this.before_.unshift(beforeEachFunction); 2127 | }; 2128 | 2129 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2130 | afterEachFunction.typeName = 'afterEach'; 2131 | this.after_.unshift(afterEachFunction); 2132 | }; 2133 | 2134 | jasmine.Suite.prototype.results = function() { 2135 | return this.queue.results(); 2136 | }; 2137 | 2138 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2139 | this.children_.push(suiteOrSpec); 2140 | if (suiteOrSpec instanceof jasmine.Suite) { 2141 | this.suites_.push(suiteOrSpec); 2142 | this.env.currentRunner().addSuite(suiteOrSpec); 2143 | } else { 2144 | this.specs_.push(suiteOrSpec); 2145 | } 2146 | this.queue.add(suiteOrSpec); 2147 | }; 2148 | 2149 | jasmine.Suite.prototype.specs = function() { 2150 | return this.specs_; 2151 | }; 2152 | 2153 | jasmine.Suite.prototype.suites = function() { 2154 | return this.suites_; 2155 | }; 2156 | 2157 | jasmine.Suite.prototype.children = function() { 2158 | return this.children_; 2159 | }; 2160 | 2161 | jasmine.Suite.prototype.execute = function(onComplete) { 2162 | var self = this; 2163 | this.queue.start(function () { 2164 | self.finish(onComplete); 2165 | }); 2166 | }; 2167 | jasmine.WaitsBlock = function(env, timeout, spec) { 2168 | this.timeout = timeout; 2169 | jasmine.Block.call(this, env, null, spec); 2170 | }; 2171 | 2172 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2173 | 2174 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2175 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2176 | this.env.setTimeout(function () { 2177 | onComplete(); 2178 | }, this.timeout); 2179 | }; 2180 | /** 2181 | * A block which waits for some condition to become true, with timeout. 2182 | * 2183 | * @constructor 2184 | * @extends jasmine.Block 2185 | * @param {jasmine.Env} env The Jasmine environment. 2186 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2187 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2188 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2189 | * @param {jasmine.Spec} spec The Jasmine spec. 2190 | */ 2191 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2192 | this.timeout = timeout || env.defaultTimeoutInterval; 2193 | this.latchFunction = latchFunction; 2194 | this.message = message; 2195 | this.totalTimeSpentWaitingForLatch = 0; 2196 | jasmine.Block.call(this, env, null, spec); 2197 | }; 2198 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2199 | 2200 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2201 | 2202 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2203 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2204 | var latchFunctionResult; 2205 | try { 2206 | latchFunctionResult = this.latchFunction.apply(this.spec); 2207 | } catch (e) { 2208 | this.spec.fail(e); 2209 | onComplete(); 2210 | return; 2211 | } 2212 | 2213 | if (latchFunctionResult) { 2214 | onComplete(); 2215 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2216 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2217 | this.spec.fail({ 2218 | name: 'timeout', 2219 | message: message 2220 | }); 2221 | 2222 | this.abort = true; 2223 | onComplete(); 2224 | } else { 2225 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2226 | var self = this; 2227 | this.env.setTimeout(function() { 2228 | self.execute(onComplete); 2229 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2230 | } 2231 | }; 2232 | // Mock setTimeout, clearTimeout 2233 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 2234 | 2235 | jasmine.FakeTimer = function() { 2236 | this.reset(); 2237 | 2238 | var self = this; 2239 | self.setTimeout = function(funcToCall, millis) { 2240 | self.timeoutsMade++; 2241 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 2242 | return self.timeoutsMade; 2243 | }; 2244 | 2245 | self.setInterval = function(funcToCall, millis) { 2246 | self.timeoutsMade++; 2247 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 2248 | return self.timeoutsMade; 2249 | }; 2250 | 2251 | self.clearTimeout = function(timeoutKey) { 2252 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2253 | }; 2254 | 2255 | self.clearInterval = function(timeoutKey) { 2256 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 2257 | }; 2258 | 2259 | }; 2260 | 2261 | jasmine.FakeTimer.prototype.reset = function() { 2262 | this.timeoutsMade = 0; 2263 | this.scheduledFunctions = {}; 2264 | this.nowMillis = 0; 2265 | }; 2266 | 2267 | jasmine.FakeTimer.prototype.tick = function(millis) { 2268 | var oldMillis = this.nowMillis; 2269 | var newMillis = oldMillis + millis; 2270 | this.runFunctionsWithinRange(oldMillis, newMillis); 2271 | this.nowMillis = newMillis; 2272 | }; 2273 | 2274 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2275 | var scheduledFunc; 2276 | var funcsToRun = []; 2277 | for (var timeoutKey in this.scheduledFunctions) { 2278 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 2279 | if (scheduledFunc != jasmine.undefined && 2280 | scheduledFunc.runAtMillis >= oldMillis && 2281 | scheduledFunc.runAtMillis <= nowMillis) { 2282 | funcsToRun.push(scheduledFunc); 2283 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 2284 | } 2285 | } 2286 | 2287 | if (funcsToRun.length > 0) { 2288 | funcsToRun.sort(function(a, b) { 2289 | return a.runAtMillis - b.runAtMillis; 2290 | }); 2291 | for (var i = 0; i < funcsToRun.length; ++i) { 2292 | try { 2293 | var funcToRun = funcsToRun[i]; 2294 | this.nowMillis = funcToRun.runAtMillis; 2295 | funcToRun.funcToCall(); 2296 | if (funcToRun.recurring) { 2297 | this.scheduleFunction(funcToRun.timeoutKey, 2298 | funcToRun.funcToCall, 2299 | funcToRun.millis, 2300 | true); 2301 | } 2302 | } catch(e) { 2303 | } 2304 | } 2305 | this.runFunctionsWithinRange(oldMillis, nowMillis); 2306 | } 2307 | }; 2308 | 2309 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2310 | this.scheduledFunctions[timeoutKey] = { 2311 | runAtMillis: this.nowMillis + millis, 2312 | funcToCall: funcToCall, 2313 | recurring: recurring, 2314 | timeoutKey: timeoutKey, 2315 | millis: millis 2316 | }; 2317 | }; 2318 | 2319 | /** 2320 | * @namespace 2321 | */ 2322 | jasmine.Clock = { 2323 | defaultFakeTimer: new jasmine.FakeTimer(), 2324 | 2325 | reset: function() { 2326 | jasmine.Clock.assertInstalled(); 2327 | jasmine.Clock.defaultFakeTimer.reset(); 2328 | }, 2329 | 2330 | tick: function(millis) { 2331 | jasmine.Clock.assertInstalled(); 2332 | jasmine.Clock.defaultFakeTimer.tick(millis); 2333 | }, 2334 | 2335 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 2336 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2337 | }, 2338 | 2339 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2340 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2341 | }, 2342 | 2343 | useMock: function() { 2344 | if (!jasmine.Clock.isInstalled()) { 2345 | var spec = jasmine.getEnv().currentSpec; 2346 | spec.after(jasmine.Clock.uninstallMock); 2347 | 2348 | jasmine.Clock.installMock(); 2349 | } 2350 | }, 2351 | 2352 | installMock: function() { 2353 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2354 | }, 2355 | 2356 | uninstallMock: function() { 2357 | jasmine.Clock.assertInstalled(); 2358 | jasmine.Clock.installed = jasmine.Clock.real; 2359 | }, 2360 | 2361 | real: { 2362 | setTimeout: jasmine.getGlobal().setTimeout, 2363 | clearTimeout: jasmine.getGlobal().clearTimeout, 2364 | setInterval: jasmine.getGlobal().setInterval, 2365 | clearInterval: jasmine.getGlobal().clearInterval 2366 | }, 2367 | 2368 | assertInstalled: function() { 2369 | if (!jasmine.Clock.isInstalled()) { 2370 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2371 | } 2372 | }, 2373 | 2374 | isInstalled: function() { 2375 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 2376 | }, 2377 | 2378 | installed: null 2379 | }; 2380 | jasmine.Clock.installed = jasmine.Clock.real; 2381 | 2382 | //else for IE support 2383 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 2384 | if (jasmine.Clock.installed.setTimeout.apply) { 2385 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2386 | } else { 2387 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2388 | } 2389 | }; 2390 | 2391 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 2392 | if (jasmine.Clock.installed.setInterval.apply) { 2393 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 2394 | } else { 2395 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 2396 | } 2397 | }; 2398 | 2399 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 2400 | if (jasmine.Clock.installed.clearTimeout.apply) { 2401 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2402 | } else { 2403 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 2404 | } 2405 | }; 2406 | 2407 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 2408 | if (jasmine.Clock.installed.clearTimeout.apply) { 2409 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2410 | } else { 2411 | return jasmine.Clock.installed.clearInterval(timeoutKey); 2412 | } 2413 | }; 2414 | 2415 | 2416 | jasmine.version_= { 2417 | "major": 1, 2418 | "minor": 0, 2419 | "build": 2, 2420 | "revision": 1298837858 2421 | }; 2422 | -------------------------------------------------------------------------------- /examples/regressionRunner.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Test Runner - regression 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | 38 |
39 | 40 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/runner-fail.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Test Runner - suite that fails 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/runner-pass.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Test Runner - suite that passes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 5 26 | 2 27 | X 28 |
29 | 30 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/spec/example-fail_spec.js: -------------------------------------------------------------------------------- 1 | describe("Example functions (should fail)", function(){ 2 | 3 | it("Should multiply two numbers", function(){ 4 | var result = EXAMPLES.multiply(5,8); 5 | 6 | expect(result).toEqual(40); 7 | 8 | }); 9 | 10 | it("Should fail!!", function(){ 11 | expect(3).toEqual(8); 12 | }); 13 | 14 | }); -------------------------------------------------------------------------------- /examples/spec/example-pass-dom_spec.js: -------------------------------------------------------------------------------- 1 | describe("Example functions that update the DOM", function(){ 2 | it("Should add two numbers", function(){ 3 | 4 | EXAMPLES.domAdd('add_test_one_x', 'add_test_one_y', 'add_test_one_result'); 5 | var result = jQuery('#add_test_one_result').html(); 6 | 7 | expect(result).toEqual('7'); 8 | }); 9 | }); -------------------------------------------------------------------------------- /examples/spec/example-pass_spec.js: -------------------------------------------------------------------------------- 1 | describe("Example functions (should pass)", function(){ 2 | 3 | it("Should divide two numbers", function(){ 4 | var result = EXAMPLES.divide(8,2); 5 | 6 | expect(result).toEqual(4); 7 | 8 | }); 9 | 10 | }); -------------------------------------------------------------------------------- /examples/spec/regression_spec.js: -------------------------------------------------------------------------------- 1 | describe('edge cases', function(){ 2 | 3 | it('toggles radio buttons', function(){ 4 | 5 | var checkedRadioID = function(){ 6 | return $('#radio_trigger_test input:checked').attr('id'); 7 | }; 8 | 9 | expect( checkedRadioID() ).toEqual( 'radio_2' ); 10 | 11 | $('#radio_3').trigger('click'); 12 | 13 | expect( $('#radio_3').is(':checked') ).toBeTruthy(); // passes 14 | expect( checkedRadioID() ).toEqual( 'radio_3' ); // fails 15 | 16 | }); 17 | 18 | it('reacts to click events', function(){ 19 | 20 | var clicked = false; 21 | 22 | runs(function(){ 23 | $('#click_test').click(function(){ 24 | clicked = true; 25 | }); 26 | 27 | $('#click_test').trigger('click'); 28 | }); 29 | 30 | waits(10); 31 | 32 | runs(function(){ 33 | expect(clicked).toBeTruthy(); 34 | }); 35 | }); 36 | 37 | it('returns the value of a select correctly', function(){ 38 | var $select = $('#select_test select'); 39 | $select.val('one'); 40 | expect( $select[0].type ).toEqual('select-one'); 41 | expect( $select.val() ).toEqual('one'); 42 | }); 43 | 44 | }); -------------------------------------------------------------------------------- /examples/src/add-in-dom.js: -------------------------------------------------------------------------------- 1 | if(! window.EXAMPLES) window.EXAMPLES = {}; 2 | 3 | EXAMPLES.domAdd = function(xId,yId,resultId){ 4 | var x = document.getElementById('add_test_one_x').innerHTML; 5 | var y = document.getElementById('add_test_one_y').innerHTML; 6 | document.getElementById('add_test_one_result').innerHTML = (+x) + (+y); 7 | }; -------------------------------------------------------------------------------- /examples/src/divide.js: -------------------------------------------------------------------------------- 1 | if(! window.EXAMPLES) window.EXAMPLES = {}; 2 | 3 | EXAMPLES.divide = function(x,y){ 4 | return x/y; 5 | } -------------------------------------------------------------------------------- /examples/src/multiply.js: -------------------------------------------------------------------------------- 1 | if(! window.EXAMPLES) window.EXAMPLES = {}; 2 | 3 | EXAMPLES.multiply = function(x,y){ 4 | return x*y; 5 | } -------------------------------------------------------------------------------- /examples/src/regression.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | 4 | })(jQuery); -------------------------------------------------------------------------------- /lib/args2options.js: -------------------------------------------------------------------------------- 1 | module.exports = function (argv) { 2 | 3 | function getArguments(args){ 4 | var unprocessed = args, 5 | processed = {}, 6 | key = "", 7 | value = ""; 8 | 9 | for(var i = 0; i < unprocessed.length; i++){ 10 | var arg = unprocessed[i]; 11 | if(arg.substr(0,2) == "--"){ 12 | if(key){ 13 | processed[key] = value; 14 | key = ""; 15 | value = ""; 16 | } 17 | key = arg.substr(2); 18 | } else { 19 | if(key) value += (value?" ":"") + arg; 20 | } 21 | } 22 | 23 | if(key){ 24 | processed[key] = value; 25 | key = ""; 26 | value = ""; 27 | } 28 | 29 | return processed; 30 | } 31 | 32 | var args = getArguments(argv); 33 | 34 | var options = { 35 | format: args.format || "default", 36 | output: args.output || null, 37 | debug: "debug" in args, 38 | runner: args.runner, 39 | server: "server" in args, 40 | port: args.server, 41 | help: "help" in args, 42 | refreshInterval: args.refresh, 43 | routeconsole: "routeconsole" in args, 44 | config: args.config 45 | }; 46 | 47 | function _getAbsolutePath(file, base){ 48 | var path; 49 | base = base || process.cwd(); 50 | if ( file.substring(0,1) == "/" || file.match(/^\w:\\/) ){ 51 | path = file; 52 | } else { 53 | path = base + '/' + file; 54 | } 55 | var absolute = require('path').normalize(path); 56 | return absolute; 57 | } 58 | 59 | function _getUsage(){ 60 | return "Usage: node run.js --runner | --config [--format simple|nice|detailed|json|html|junit] [--output ] [--server [port] [--refresh ]] [--routeconsole] [--help]\n\n" + 61 | "For more information, visit https://github.com/andrewpmckenzie/node-jasmine-dom"; 62 | } 63 | 64 | function _formatNice(obj){ 65 | var message, k, details; 66 | if(obj.failed === 0){ 67 | message = "====== PASSED ====== \n"; 68 | for( k in obj.details ){ 69 | details = obj.details[k]; 70 | message += details.name + " - " + details.total + " tests \n"; 71 | } 72 | } else { 73 | message = "====== FAILED ====== \n"; 74 | for( k in obj.failureDetails ){ 75 | details = obj.failureDetails[k]; 76 | message += " - In " + details.group + " >> " + details.suite + " >> " + details.spec + " :: " + details.message + "\n"; 77 | } 78 | } 79 | 80 | return message; 81 | } 82 | 83 | function _formatDetailed(obj){ 84 | var message = ""; 85 | if(obj.failed === 0){ 86 | message = "\n====== PASSED ====== \n\n"; 87 | } else { 88 | message = "\n====== FAILED ====== \n\n"; 89 | } 90 | 91 | for( var k in obj.details ){ 92 | var details = obj.details[k]; 93 | var passMessage = ''; 94 | var failMessage = ''; 95 | var failure; 96 | 97 | for( var kpasses in details.passes){ 98 | passMessage += " - "+details.passes[kpasses]+" \n"; 99 | } 100 | for( var kfailures in details.failureDetails){ 101 | failure = details.failureDetails[kfailures]; 102 | failMessage += " - "+kfailures+" \n"; 103 | for( var ifailedtests in failure){ 104 | failMessage += " ["+failure[ifailedtests].message+"] \n"; 105 | } 106 | } 107 | 108 | var name = details.suites[0]; 109 | message += name + " - " + details.total + " tests \n"; 110 | message += " PASSES \n"; 111 | message += passMessage || " (none) \n"; 112 | message += " FAILURES \n"; 113 | message += failMessage || " (none) \n"; 114 | } 115 | 116 | if(obj.failed === 0){ 117 | message = message + "\n====== PASSED ====== \n\n"; 118 | } else { 119 | message = message + "\n====== FAILED ====== \n\n"; 120 | } 121 | 122 | return message; 123 | } 124 | 125 | function _format(report){ 126 | var result = ''; 127 | 128 | switch(options.format){ 129 | case 'simple': 130 | case 'default': 131 | result = report.simple.status; 132 | break; 133 | case 'json': 134 | result = JSON.stringify(report.simple); 135 | break; 136 | case 'nice': 137 | result = _formatNice(report.simple); 138 | break; 139 | case 'html': 140 | result = report.html; 141 | break; 142 | case 'junit': 143 | result = report.junit; 144 | break; 145 | case 'detailed': 146 | result = _formatDetailed(report.detailed); 147 | break; 148 | default: 149 | console.error("Unknown format:" + options.format); 150 | process.exit(0); 151 | break; 152 | } 153 | 154 | return result; 155 | } 156 | 157 | function _output(text){ 158 | if(options.output){ 159 | var file = _getAbsolutePath(options.output); 160 | require('fs').writeFile(file,text,function(err){ 161 | if(err) console.log("Something went wrong writing the report to disk: ",err); 162 | }); 163 | } else if(options.server) { 164 | return; 165 | } else { 166 | console.log(text); 167 | } 168 | } 169 | 170 | function _processReport(report){ 171 | var text = _format(report); 172 | _output(text); 173 | } 174 | 175 | function _parseConfig(config){ 176 | var fs = require('fs'), 177 | path = require('path'); 178 | 179 | var configPath = _getAbsolutePath(config); 180 | var configBase = path.normalize(path.dirname(configPath)); 181 | 182 | var file = fs.readFileSync(config,'utf8'); 183 | 184 | var runnerConfig = []; 185 | try { 186 | var yaml = require('yaml').eval(file); 187 | 188 | for(var k in yaml){ 189 | var runner = yaml[k]; 190 | if("runner" in runner){ 191 | runnerConfig.push({ 192 | name: runner.name, 193 | runner: _getAbsolutePath(runner.runner, configBase) 194 | }); 195 | } 196 | } 197 | 198 | if(runnerConfig.length === 0) console.error("No runners specified in config. See examples/config.yaml for format."); 199 | 200 | } catch (e) { 201 | console.log("Invalid config file.\n\n"); 202 | console.error(e); 203 | process.exit(1); 204 | } 205 | 206 | return runnerConfig; 207 | } 208 | 209 | function validate(options){ 210 | if (options.help){ 211 | console.log(_getUsage()); 212 | process.exit(0); 213 | } 214 | if (! options.runner && ! options.config) { 215 | console.error("You need to specify a html --runner file.\n"); 216 | console.log(_getUsage()); 217 | process.exit(0); 218 | } 219 | } 220 | 221 | function getRunners(runner, config){ 222 | if(config){ 223 | return _parseConfig(config); 224 | } else { 225 | return [ _getAbsolutePath(runner) ]; 226 | } 227 | } 228 | 229 | validate(options); 230 | 231 | return { 232 | runners: getRunners(options.runner, options.config), 233 | serve: options.server, 234 | port: options.port, 235 | refreshInterval: options.refreshInterval, 236 | debug: options.debug, 237 | routeConsole: options.routeconsole, 238 | onDone: function(report){ 239 | _processReport(report); 240 | } 241 | }; 242 | }; 243 | -------------------------------------------------------------------------------- /lib/jasmine-dom/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'); 3 | 4 | function JasmineRunner(options, callback){ 5 | 6 | var doServer = options.serve, 7 | port = options.port || 8081, 8 | refreshInterval = options.refreshInterval || 2000, 9 | runners = options.runners || null, 10 | routeConsole = options.routeConsole, 11 | that = this; 12 | 13 | this.onError = options.onError || function(msg){ console.error(msg); }, 14 | this.isRunningTests = false; 15 | this.isTestRunQueued = false; 16 | 17 | this.reporter = this._setupReporter(function(){ 18 | 19 | callback(); 20 | 21 | }); // async 22 | 23 | if(doServer){ 24 | this.server = that._setupServer({ 25 | port: port, 26 | reporter: this.reporter, 27 | refreshInterval: refreshInterval 28 | }); 29 | } 30 | 31 | if(runners){ 32 | this.tests = this._setupRunners({ 33 | runners: runners, 34 | jasmineReporter: this.reporter.getJasmineReporter(), 35 | routeConsole: routeConsole 36 | }); 37 | } else { 38 | this.onError('I don`t have any tests to run!'); 39 | } 40 | } 41 | 42 | JasmineRunner.prototype._setupReporter = function(callback){ 43 | console.njddebug("Setting up reporter"); 44 | 45 | var reporter = require('./reporter-agregator.js').create({ 46 | onDone: function(){ 47 | console.njddebug("Reporter created."); 48 | callback(); 49 | } 50 | }); 51 | 52 | return reporter; 53 | 54 | }; 55 | 56 | JasmineRunner.prototype._setupServer = function(options){ 57 | console.njddebug("Setting up server"); 58 | var that = this, 59 | server = require('./server.js'); 60 | 61 | server.start(options.port, function(){ 62 | callback(server); 63 | }); 64 | 65 | options.reporter.onUpdate(function(report){ 66 | server.updateHtml(report.html); 67 | server.updateJson(report.simple); 68 | 69 | console.njddebug("Tests will run again in " + (options.refreshInterval/1000) + " seconds."); 70 | setTimeout(function(){ 71 | that.runTests(); 72 | }, options.refreshInterval); 73 | }); 74 | 75 | return server; 76 | }; 77 | 78 | JasmineRunner.prototype._setupRunners = function(options,callback){ 79 | console.njddebug("Setting up tests."); 80 | 81 | var tests = []; 82 | var runners = options.runners || [], 83 | jasmineReporter = options.jasmineReporter, 84 | routeConsole = options.routeConsole; 85 | 86 | for(var k in runners){ 87 | var runner = runners[k]; 88 | console.njddebug('Creating test based on ' + runner); 89 | 90 | var test = require('./runner-from-html.js').create({ 91 | runner: runner, 92 | jasmineReporter: jasmineReporter, 93 | routeConsole: routeConsole, 94 | onError: this.onError 95 | }); 96 | tests.push(test); 97 | } 98 | 99 | console.njddebug("Finished creating tests ( based on " +tests.length + " runner/s)."); 100 | 101 | return tests; 102 | }; 103 | 104 | JasmineRunner.prototype.runTests = function(callback){ 105 | 106 | var that = this; 107 | 108 | // We have to maintain synchronisity, otherwise things 109 | // get hectic. 110 | if(this.isRunningTests) { 111 | // DANGER: if more than one call to runTests is made when 112 | // tests are running, only the last callback will ever be 113 | // triggered. 114 | this.isTestRunQueued = true; 115 | this.queuedCallback = callback; 116 | console.njddebug("Cannot run tests concurrently. Queued another run."); 117 | return; 118 | } 119 | var whenAllTestsHaveRun = function(){ 120 | console.njddebug("Finished running tests."); 121 | 122 | if(callback) callback(that.reporter.getReport()); 123 | that.reporter.reset(); 124 | 125 | that.isRunningTests = false; 126 | if(that.isTestRunQueued){ 127 | that.isTestRunQueued = false; 128 | that.runTests(that.queuedCallback); 129 | that.queuedCallback = false; 130 | } 131 | }; 132 | 133 | console.njddebug("Running tests."); 134 | this.isRunningTests = true; 135 | 136 | var queue = [], 137 | tests = this.tests; 138 | for(var i = 0; i < tests.length; i++) queue.push(tests[i]); 139 | 140 | // !! Is recursive 141 | var runNextTest = function(){ 142 | if(queue.length === 0){ 143 | whenAllTestsHaveRun(); 144 | return; 145 | } 146 | 147 | var test = queue.pop(); 148 | console.njddebug("Running " + test.name); 149 | test.run(function(){ 150 | console.njddebug("Finished running " + test.name); 151 | runNextTest(); 152 | }); 153 | }; 154 | 155 | runNextTest(); 156 | }; 157 | 158 | exports.run = function(options, callback){ 159 | var onDone = options.onDone || function(){}, 160 | debug = options.debug; 161 | 162 | console.njddebug = debug ? function(msg){ console.log(msg); } : function(msg){}; 163 | 164 | var runner = new JasmineRunner(options, function() { 165 | runner.runTests(onDone); 166 | if (callback) callback( runner ); 167 | }); 168 | 169 | }; 170 | -------------------------------------------------------------------------------- /lib/jasmine-dom/reporter-agregator.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var cp = function(o){ if(typeof o != 'object') return o; var n = {}; for(var k in o) n[k] = o[k]; return n; }; 3 | 4 | function Reporter(options){ 5 | 6 | var onDone = options.onDone || function(){}; 7 | 8 | /** 9 | * A reporter should implement the jasmine reporter interface 10 | * as well as the following properties: 11 | * - {string} format e.g. 'html' 12 | * - {function} getReport() 13 | * - {function} updateReport() 14 | * - {function} reset() 15 | */ 16 | this._updateListeners = []; 17 | 18 | this.report = {}; 19 | this._jasmineReporters = {}; 20 | this._jasmineReporters['simpleReporter'] = require('./reporter-simple').create(); 21 | this._jasmineReporters['detailedReporter'] = require('./reporter-detailed').create(); 22 | this._jasmineReporters['junitReporter'] = require('./reporter-junit').create(); 23 | this._jasmineReporters['htmlReporter'] = require('./reporter-html').create({ 24 | jasminePath: path.normalize(__dirname+'/resources/jasmine.js'), 25 | jasmineHtmlPath: path.normalize(__dirname+'/resources/jasmine-html.js'), 26 | skeletonPath: path.normalize(__dirname+'/resources/skeleton.html') 27 | }, onDone); 28 | } 29 | 30 | Reporter.prototype._updateReport = function(){ 31 | // tell reporters to update themselves 32 | for(var k in this._jasmineReporters){ 33 | var reporter = this._jasmineReporters[k]; 34 | reporter.updateReport(); 35 | this.report[reporter.format] = reporter.getReport(); 36 | } 37 | 38 | for(var i = 0; i < this._updateListeners.length; i++){ 39 | this._updateListeners[i]( cp(this.report) ); 40 | } 41 | }; 42 | 43 | Reporter.prototype.getJasmineReporter = function(){ 44 | var that = this; 45 | return { 46 | log : function(str){ 47 | for(var k in that._jasmineReporters){ 48 | var reporter = that._jasmineReporters[k]; 49 | if(reporter.log) reporter.log(str); 50 | } 51 | }, 52 | reportSpecStarting : function(runner) { 53 | for(var k in that._jasmineReporters){ 54 | var reporter = that._jasmineReporters[k]; 55 | if(reporter.reportSpecStarting) reporter.reportSpecStarting(runner); 56 | } 57 | }, 58 | reportRunnerStarting : function(runner) { 59 | for(var k in that._jasmineReporters){ 60 | var reporter = that._jasmineReporters[k]; 61 | if(reporter.reportRunnerStarting) reporter.reportRunnerStarting(runner); 62 | } 63 | }, 64 | reportSuiteResults : function(suite) { 65 | for(var k in that._jasmineReporters){ 66 | var reporter = that._jasmineReporters[k]; 67 | if(reporter.reportSuiteResults) reporter.reportSuiteResults(suite); 68 | } 69 | }, 70 | reportSpecResults : function(spec) { 71 | for(var k in that._jasmineReporters){ 72 | var reporter = that._jasmineReporters[k]; 73 | if(reporter.reportSpecResults) reporter.reportSpecResults(spec); 74 | } 75 | }, 76 | reportRunnerResults : function(runner) { 77 | for(var k in that._jasmineReporters){ 78 | var reporter = that._jasmineReporters[k]; 79 | if(reporter.reportRunnerResults) reporter.reportRunnerResults(runner); 80 | } 81 | that._updateReport(); 82 | }, 83 | reportStartingGroup : function(name){ 84 | for(var k in that._jasmineReporters){ 85 | var reporter = that._jasmineReporters[k]; 86 | if(reporter.reportStartingGroup) reporter.reportStartingGroup(name); 87 | } 88 | } 89 | }; 90 | }; 91 | 92 | Reporter.prototype.onUpdate = function(callback){ 93 | this._updateListeners.push(callback); 94 | }; 95 | 96 | Reporter.prototype.getReport = function(){ 97 | return this.report; 98 | }; 99 | 100 | Reporter.prototype.reset = function(){ 101 | 102 | for(var k in this._jasmineReporters){ 103 | var reporter = this._jasmineReporters[k]; 104 | reporter.reset(); 105 | } 106 | }; 107 | 108 | exports.create = function(options){ 109 | var reporter = new Reporter(options); 110 | return reporter; 111 | }; 112 | -------------------------------------------------------------------------------- /lib/jasmine-dom/reporter-detailed.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | function DetailedReporter(opt){ 4 | console.njddebug('Creating DetailedReporter'); 5 | 6 | var that = this; 7 | 8 | this.format = 'detailed'; 9 | this._report = {}; 10 | 11 | this.results = []; 12 | this.currentSet = 0; 13 | 14 | }; 15 | 16 | DetailedReporter.prototype.getReport = function(){ 17 | return _.clone(this._report); 18 | }; 19 | 20 | DetailedReporter.prototype.reportStartingGroup = function(name){ 21 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 22 | this.results[this.currentSet].name = name; 23 | }; 24 | 25 | DetailedReporter.prototype.reportSpecResults = function(spec){ 26 | 27 | var items = spec.results().getItems(); 28 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 29 | this.results[this.currentSet].failureDetails = this.results[this.currentSet].failureDetails || {}; 30 | 31 | for (var i = 0; i < items.length; i++){ 32 | if( ! items[i].passed_){ 33 | var failureReport = items[i]; 34 | var specName = failureReport.spec; 35 | this.results[this.currentSet].failureDetails[specName] = this.results[this.currentSet].failureDetails[specName] || []; 36 | this.results[this.currentSet].failureDetails[specName].push( failureReport ); 37 | } 38 | } 39 | }; 40 | 41 | DetailedReporter.prototype.reportRunnerResults = function(runner){ 42 | var results = runner.results(); 43 | var specs = runner.specs(); 44 | var suites = runner.suites(); 45 | 46 | var passes = []; 47 | var failures = []; 48 | var suiteNames = []; 49 | 50 | for(var i = 0; i < suites.length; i++ ){ 51 | suiteNames.push( suites[i].description ) 52 | }; 53 | 54 | for(var i = 0; i < specs.length; i++){ 55 | var result = specs[i].results(); 56 | if(result.failedCount > 0){ 57 | failures.push( result.description ); 58 | } else { 59 | passes.push( result.description ); 60 | } 61 | } 62 | 63 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 64 | this.results[this.currentSet].passes = _.clone(passes); 65 | this.results[this.currentSet].failures = _.clone(failures); 66 | this.results[this.currentSet].suites = _.clone(suiteNames); 67 | this.results[this.currentSet].passed = results.passedCount; 68 | this.results[this.currentSet].failed = results.failedCount; 69 | this.results[this.currentSet].total = results.totalCount; 70 | this.currentSet++; 71 | }; 72 | 73 | DetailedReporter.prototype.updateReport = function(){ 74 | var totalPassed = 0, 75 | totalFailed = 0, 76 | totalTests = 0, 77 | totalSuites = 0, 78 | failureDetails = [], 79 | passDetails = []; 80 | 81 | for(var k in this.results){ 82 | var result = this.results[k]; 83 | totalPassed += result.passed; 84 | totalFailed += result.failed; 85 | totalTests += result.total; 86 | totalSuites++; 87 | if(result.failures){ 88 | for(var j = 0; j < result.failures.length; j++){ 89 | failureDetails.push(result.failures[j]); 90 | } 91 | } 92 | if(result.passes){ 93 | for(var j = 0; j < result.passes.length; j++){ 94 | passDetails.push(result.passes[j]); 95 | } 96 | } 97 | } 98 | 99 | this._report = { 100 | details: _.clone(this.results), 101 | passed: totalPassed, 102 | failed: totalFailed, 103 | total: totalTests, 104 | suites: totalSuites, 105 | failureDetails: _.clone(failureDetails), 106 | passDetails: _.clone(passDetails), 107 | status: (totalFailed == 0) ? "Passed" : "Failed" 108 | }; 109 | }; 110 | 111 | DetailedReporter.prototype.reset = function(){ 112 | this.results = []; 113 | }; 114 | 115 | 116 | 117 | exports.create = function(opt){ 118 | return new DetailedReporter(opt); 119 | }; 120 | -------------------------------------------------------------------------------- /lib/jasmine-dom/reporter-html.js: -------------------------------------------------------------------------------- 1 | function HtmlReporter(options, callback){ 2 | console.njddebug('Creating HtmlReporter'); 3 | 4 | this.format = 'html'; 5 | this._report = ''; 6 | this._window = {}; 7 | 8 | var jasmine = options.jasminePath, 9 | jasmineHtml = options.jasmineHtmlPath, 10 | skeleton = options.skeletonPath, 11 | that = this; 12 | 13 | console.njddebug('Constructing HtmlReporter DOM'); 14 | require('jsdom').env(skeleton, [ jasmine, jasmineHtml ], function (errors, window) { 15 | if(errors) console.error('Error construction DOM for html reporter: ',errors); 16 | 17 | console.njddebug('Creating TrivialReporter instance.'); 18 | var trivialReporter = new window.jasmine.TrivialReporter(); 19 | 20 | console.njddebug('Transferring TrivialReporter methods to HtmlReporter object'); 21 | for(var k in trivialReporter) that[k] = trivialReporter[k]; 22 | 23 | that._window = window; 24 | 25 | console.njddebug('Done creating HtmlReporter'); 26 | callback(that); 27 | }); 28 | }; 29 | HtmlReporter.prototype.updateReport = function(){ 30 | this._report = this._window.document.outerHTML; 31 | }; 32 | HtmlReporter.prototype.getReport = function(){ 33 | return this._report; 34 | }; 35 | HtmlReporter.prototype.reset = function(){ 36 | this._window.document.body.innerHTML = ''; // clear for next report 37 | }; 38 | 39 | exports.create = function(options,callback){ 40 | return new HtmlReporter(options,callback); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/jasmine-dom/reporter-junit.js: -------------------------------------------------------------------------------- 1 | function JunitXmlReporter(opt){ 2 | console.njddebug('Creating JunitXmlReporter'); 3 | 4 | var that = this; 5 | 6 | this.format = 'junit'; 7 | this.report = ''; 8 | 9 | this.xml = ''; 10 | this.currentSetData = ''; 11 | this.currentGroupName = ''; 12 | 13 | }; 14 | 15 | JunitXmlReporter.prototype.getReport = function(){ 16 | return this.report; 17 | }; 18 | 19 | JunitXmlReporter.prototype.reportSpecResults = function(spec){ 20 | var s = function(s){ 21 | return s.replace(/\s/gi, '_'); 22 | }; 23 | var e = function(s){ 24 | return s.replace(/\/gi, '>').replace(/"/gi, "'"); 25 | } 26 | 27 | var specName = s(this.currentGroupName) + "." + s(spec.suite.description) + "." + s(spec.description); 28 | 29 | var results = spec.results().getItems(); 30 | for(var k in results){ 31 | var result = results[k]; 32 | var name = result.type + " " + result.matcherName + " " + (result.expected ? result.expected : ""); 33 | name = e(name); 34 | specName = e(specName); 35 | if(result.passed()){ 36 | this.xml += ''; 37 | } else { 38 | this.xml += ''; 39 | this.xml += ''; 43 | this.xml += ''; 44 | } 45 | } 46 | }; 47 | 48 | JunitXmlReporter.prototype.reportSuiteResults = function(suite){ 49 | 50 | }; 51 | 52 | JunitXmlReporter.prototype.reportRunnerResults = function(runner){ 53 | 54 | }; 55 | 56 | JunitXmlReporter.prototype.reportStartingGroup = function(name){ 57 | this.currentGroupName = name; 58 | }; 59 | 60 | JunitXmlReporter.prototype.updateReport = function(){ 61 | this.report = ""+this.xml+""; 62 | }; 63 | 64 | JunitXmlReporter.prototype.reset = function(){ 65 | this.results = []; 66 | }; 67 | 68 | 69 | 70 | exports.create = function(opt){ 71 | return new JunitXmlReporter(opt); 72 | } -------------------------------------------------------------------------------- /lib/jasmine-dom/reporter-simple.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | function SimpleReporter(opt){ 4 | console.njddebug('Creating SimpleReporter'); 5 | 6 | var that = this; 7 | 8 | this.format = 'simple'; 9 | this._report = {}; 10 | 11 | this.results = []; 12 | this.currentSet = 0; 13 | 14 | }; 15 | 16 | SimpleReporter.prototype.getReport = function(){ 17 | return _.clone(this._report); 18 | }; 19 | 20 | SimpleReporter.prototype.reportStartingGroup = function(name){ 21 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 22 | this.results[this.currentSet].name = name; 23 | }; 24 | SimpleReporter.prototype.reportSpecResults = function(spec){ 25 | 26 | var items = spec.results().getItems(); 27 | for (var i = 0; i < items.length; i++){ 28 | if( ! items[i].passed_){ 29 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 30 | this.results[this.currentSet].failures = this.results[this.currentSet].failures || []; 31 | 32 | var failureReport = items[i]; 33 | failureReport.suite = spec.suite.description; 34 | failureReport.spec = spec.description; 35 | failureReport.group = this.results[this.currentSet].name; 36 | this.results[this.currentSet].failures.push(failureReport); 37 | } 38 | } 39 | }; 40 | 41 | SimpleReporter.prototype.reportRunnerResults = function(runner){ 42 | var results = runner.results(); 43 | 44 | this.results[this.currentSet] = this.results[this.currentSet] || {}; 45 | this.results[this.currentSet].passed = results.passedCount; 46 | this.results[this.currentSet].failed = results.failedCount; 47 | this.results[this.currentSet].total = results.totalCount; 48 | this.currentSet++; 49 | }; 50 | 51 | SimpleReporter.prototype.updateReport = function(){ 52 | var totalPassed = 0, 53 | totalFailed = 0, 54 | totalTests = 0, 55 | totalSuites = 0, 56 | failureDetails = []; 57 | 58 | for(var k in this.results){ 59 | var result = this.results[k]; 60 | totalPassed += result.passed; 61 | totalFailed += result.failed; 62 | totalTests += result.total; 63 | totalSuites++; 64 | if(result.failures){ 65 | for(var j = 0; j < result.failures.length; j++){ 66 | failureDetails.push(result.failures[j]); 67 | } 68 | } 69 | } 70 | 71 | this._report = { 72 | details: _.clone(this.results), 73 | passed: totalPassed, 74 | failed: totalFailed, 75 | total: totalTests, 76 | suites: totalSuites, 77 | failureDetails: _.clone(failureDetails), 78 | status: (totalFailed == 0) ? "Passed" : "Failed" 79 | }; 80 | }; 81 | 82 | SimpleReporter.prototype.reset = function(){ 83 | this.results = []; 84 | }; 85 | 86 | 87 | 88 | exports.create = function(opt){ 89 | return new SimpleReporter(opt); 90 | }; 91 | -------------------------------------------------------------------------------- /lib/jasmine-dom/resources/jasmine-html.js: -------------------------------------------------------------------------------- 1 | // NOTE: contains modifications for jasmine-dom 2 | 3 | jasmine.TrivialReporter = function(doc) { 4 | this.document = doc || document; 5 | this.suiteDivs = {}; 6 | this.logRunningSpecs = false; 7 | }; 8 | 9 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 10 | var el = document.createElement(type); 11 | 12 | for (var i = 2; i < arguments.length; i++) { 13 | var child = arguments[i]; 14 | 15 | if (typeof child === 'string') { 16 | el.appendChild(document.createTextNode(child)); 17 | } else { 18 | if (child) { el.appendChild(child); } 19 | } 20 | } 21 | 22 | for (var attr in attrs) { 23 | if (attr == "className") { 24 | el[attr] = attrs[attr]; 25 | } else { 26 | el.setAttribute(attr, attrs[attr]); 27 | } 28 | } 29 | 30 | return el; 31 | }; 32 | 33 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 34 | var showPassed, showSkipped; 35 | 36 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, 37 | this.createDom('div', { className: 'banner' }, 38 | this.createDom('div', { className: 'logo' }, 39 | this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"), 40 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 41 | this.createDom('div', { className: 'options' }, 42 | "Show ", 43 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 45 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 46 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 47 | ) 48 | ), 49 | 50 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 51 | this.createDom('span', { className: 'run_spec', href: '?' }, "run all"), 52 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 53 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 54 | ); 55 | 56 | this.document.body.appendChild(this.outerDiv); 57 | 58 | var suites = runner.suites(); 59 | for (var i = 0; i < suites.length; i++) { 60 | var suite = suites[i]; 61 | var suiteDiv = this.createDom('div', { className: 'suite' }, 62 | this.createDom('span', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 63 | this.createDom('span', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 64 | this.suiteDivs[suite.id] = suiteDiv; 65 | var parentDiv = this.outerDiv; 66 | if (suite.parentSuite) { 67 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 68 | } 69 | parentDiv.appendChild(suiteDiv); 70 | } 71 | 72 | this.startedAt = new Date(); 73 | 74 | var self = this; 75 | showPassed.onclick = function(evt) { 76 | if (showPassed.checked) { 77 | self.outerDiv.className += ' show-passed'; 78 | } else { 79 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 80 | } 81 | }; 82 | 83 | showSkipped.onclick = function(evt) { 84 | if (showSkipped.checked) { 85 | self.outerDiv.className += ' show-skipped'; 86 | } else { 87 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 88 | } 89 | }; 90 | }; 91 | 92 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 93 | var results = runner.results(); 94 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 95 | this.runnerDiv.setAttribute("class", className); 96 | //do it twice for IE 97 | this.runnerDiv.setAttribute("className", className); 98 | var specs = runner.specs(); 99 | var specCount = 0; 100 | for (var i = 0; i < specs.length; i++) { 101 | if (this.specFilter(specs[i])) { 102 | specCount++; 103 | } 104 | } 105 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 106 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 107 | this.runnerMessageSpan.replaceChild(this.createDom('span', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 108 | 109 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 110 | }; 111 | 112 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 113 | var results = suite.results(); 114 | var status = results.passed() ? 'passed' : 'failed'; 115 | if (results.totalCount == 0) { // todo: change this to check results.skipped 116 | status = 'skipped'; 117 | } 118 | this.suiteDivs[suite.id].className += " " + status; 119 | }; 120 | 121 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 122 | if (this.logRunningSpecs) { 123 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 124 | } 125 | }; 126 | 127 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 128 | var results = spec.results(); 129 | var status = results.passed() ? 'passed' : 'failed'; 130 | if (results.skipped) { 131 | status = 'skipped'; 132 | } 133 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 134 | this.createDom('span', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 135 | this.createDom('span', { 136 | className: 'description', 137 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 138 | title: spec.getFullName() 139 | }, spec.description)); 140 | 141 | 142 | var resultItems = results.getItems(); 143 | var messagesDiv = this.createDom('div', { className: 'messages' }); 144 | for (var i = 0; i < resultItems.length; i++) { 145 | var result = resultItems[i]; 146 | 147 | if (result.type == 'log') { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 149 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 150 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 151 | 152 | if (result.trace.stack) { 153 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 154 | } 155 | } 156 | } 157 | 158 | if (messagesDiv.childNodes.length > 0) { 159 | specDiv.appendChild(messagesDiv); 160 | } 161 | 162 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 163 | }; 164 | 165 | jasmine.TrivialReporter.prototype.log = function() { 166 | var console = jasmine.getGlobal().console; 167 | if (console && console.log) { 168 | if (console.log.apply) { 169 | console.log.apply(console, arguments); 170 | } else { 171 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 172 | } 173 | } 174 | }; 175 | 176 | jasmine.TrivialReporter.prototype.getLocation = function() { 177 | return this.document.location; 178 | }; 179 | 180 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 181 | 182 | return true; 183 | }; 184 | -------------------------------------------------------------------------------- /lib/jasmine-dom/resources/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 | visibility:hidden; 49 | text-align: right; 50 | font-size: .8em; 51 | } 52 | 53 | 54 | 55 | 56 | .suite { 57 | border: 1px outset gray; 58 | margin: 5px 0; 59 | padding-left: 1em; 60 | } 61 | 62 | .suite .suite { 63 | margin: 5px; 64 | } 65 | 66 | .suite.passed { 67 | background-color: #dfd; 68 | } 69 | 70 | .suite.failed { 71 | background-color: #fdd; 72 | } 73 | 74 | .spec { 75 | margin: 5px; 76 | padding-left: 1em; 77 | clear: both; 78 | } 79 | 80 | .spec.failed, .spec.passed, .spec.skipped { 81 | padding-bottom: 5px; 82 | border: 1px solid gray; 83 | } 84 | 85 | .spec.failed { 86 | background-color: #fbb; 87 | border-color: red; 88 | } 89 | 90 | .spec.passed { 91 | background-color: #bfb; 92 | border-color: green; 93 | } 94 | 95 | .spec.skipped { 96 | background-color: #bbb; 97 | } 98 | 99 | .messages { 100 | border-left: 1px dashed gray; 101 | padding-left: 1em; 102 | padding-right: 1em; 103 | } 104 | 105 | .passed { 106 | background-color: #cfc; 107 | display: none; 108 | } 109 | 110 | .failed { 111 | background-color: #fbb; 112 | } 113 | 114 | .skipped { 115 | color: #777; 116 | background-color: #eee; 117 | display: none; 118 | } 119 | 120 | 121 | /*.resultMessage {*/ 122 | /*white-space: pre;*/ 123 | /*}*/ 124 | 125 | .resultMessage span.result { 126 | display: block; 127 | line-height: 2em; 128 | color: black; 129 | } 130 | 131 | .resultMessage .mismatch { 132 | color: black; 133 | } 134 | 135 | .stackTrace { 136 | white-space: pre; 137 | font-size: .8em; 138 | margin-left: 10px; 139 | max-height: 5em; 140 | overflow: auto; 141 | border: 1px inset red; 142 | padding: 1em; 143 | background: #eef; 144 | } 145 | 146 | .finished-at { 147 | padding-left: 1em; 148 | font-size: .6em; 149 | } 150 | 151 | .show-passed .passed, 152 | .show-skipped .skipped { 153 | display: block; 154 | } 155 | 156 | 157 | #jasmine_content { 158 | position:fixed; 159 | right: 100%; 160 | } 161 | 162 | .runner { 163 | border: 1px solid gray; 164 | display: block; 165 | margin: 5px 0; 166 | padding: 2px 0 2px 10px; 167 | } 168 | -------------------------------------------------------------------------------- /lib/jasmine-dom/resources/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Loading... 6 | 55 | 56 | 57 |
58 |
59 | 60 |
61 | 62 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/jasmine-dom/resources/skeleton.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Test Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/jasmine-dom/runner-from-html.js: -------------------------------------------------------------------------------- 1 | var onError; 2 | 3 | function Runner( options ){ 4 | console.njddebug("Constructing runner for "+options.runner+"."); 5 | onError = options.onError || function(e){ console.error(e); }; 6 | 7 | var name, path, 8 | runner = options.runner; 9 | if(typeof runner == 'object'){ 10 | name = runner.name; 11 | path = runner.runner; 12 | } else { 13 | name = runner; 14 | path = runner; 15 | } 16 | 17 | this._options = { 18 | reporter: options.jasmineReporter || {}, 19 | path: require('path').normalize(path), 20 | name: name, 21 | routeConsole: options.routeConsole 22 | }; 23 | }; 24 | 25 | Runner.prototype._getScriptPaths = function(pathToHtml, callback){ 26 | var path = require('path'); 27 | require('jsdom').env(pathToHtml, [], function(errors,window){ 28 | if(errors){ 29 | console.njddebug(errors); 30 | onError('Error when constructing DOM to get script paths for runner.'); 31 | return; 32 | } 33 | 34 | var basePath = path.dirname(pathToHtml); 35 | var scripts = window.document.getElementsByTagName('script'); 36 | var scriptPaths = [] 37 | 38 | for(var i = 0; i < scripts.length; i++){ 39 | var script = scripts[i]; 40 | var src = script.getAttribute('src'); 41 | if(src) { 42 | var scriptPath = path.normalize(basePath + "/" + src); 43 | scriptPaths.push(scriptPath); 44 | } 45 | } 46 | callback(scriptPaths); 47 | }); 48 | }; 49 | 50 | Runner.prototype._executeRunner = function(pathToHtml, scripts, reporter, callback){ 51 | console.njddebug("Constructing runner with following scripts: "); 52 | for(var i = 0; i < scripts.length; i++) console.njddebug(" - " + scripts[i]); 53 | 54 | var displayedHeader = false; 55 | var displayLog = this._options.routeConsole; 56 | var log = function(type, msg){ 57 | if(! displayLog ) return; 58 | if(! displayedHeader){ 59 | console.log('=== console messages for ' + pathToHtml + ' ===') 60 | displayedHeader = true; 61 | } 62 | console.log(type + ' - ' + msg); 63 | }; 64 | 65 | require('jsdom').env(pathToHtml, scripts, function(errors,window){ 66 | window.console.log = function(msg){ log( 'LOG ', msg); }; 67 | window.console.error = function(msg){ log( 'ERROR', msg); }; 68 | window.console.warn = function(msg){ log( 'WARN ', msg); }; 69 | window.console.info = function(msg){ log( 'INFO ', msg); }; 70 | 71 | if(errors){ 72 | console.njddebug(errors); 73 | onError('Error when constructing DOM for runner.'); 74 | return; 75 | } 76 | 77 | // Executed when the DOM has finished construction 78 | if(errors) console.error('Error construction DOM for tests: ',errors); 79 | window.jasmine.getEnv().addReporter(reporter); 80 | window.jasmine.getEnv().addReporter({ 81 | reportRunnerResults : function(){ 82 | if(callback) callback(); 83 | } 84 | }); 85 | 86 | console.njddebug("Running runner in Jasmine."); 87 | window.jasmine.getEnv().execute(); 88 | }); 89 | }; 90 | 91 | Runner.prototype.run = function(callback){ 92 | var path = this._options.path, 93 | name = this._options.name, 94 | reporter = this._options.reporter, 95 | that=this; 96 | 97 | console.njddebug("Running runner " + name); 98 | if(reporter.reportStartingGroup) reporter.reportStartingGroup(name); 99 | 100 | this._getScriptPaths(path, function(scripts){ 101 | that._executeRunner(path,scripts,reporter,function(){ 102 | callback(); 103 | }); 104 | }); 105 | 106 | }; 107 | 108 | exports.create = function(options){ 109 | return new Runner(options); 110 | } 111 | -------------------------------------------------------------------------------- /lib/jasmine-dom/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var staticServe = require('node-static'); 3 | var fs = require('fs'); 4 | 5 | function Server(port){ 6 | this._html = 'Nothing has happened yet. But stay tuned!'; 7 | this._simpleHtml = fs.readFileSync(__dirname+'/resources/simple.html'); 8 | this._json = {}; 9 | this.port = port; 10 | 11 | var staticServer = new staticServe.Server(__dirname+'/resources'); 12 | 13 | var that = this; 14 | this._httpServer = http.createServer(function (request, response) { 15 | that.processRequest(request.url,request.headers['content-type'],response); 16 | 17 | // static file fallback 18 | request.addListener('end', function(){ 19 | staticServer.serve(request,response); 20 | }); 21 | }); 22 | }; 23 | 24 | Server.prototype.updateJson = function(json){ 25 | this._json = json; 26 | } 27 | 28 | Server.prototype.updateHtml = function(html){ 29 | this._jasmineHtml = html; 30 | } 31 | 32 | Server.prototype.processRequest = function(url,mime,response){ 33 | 34 | switch(url){ 35 | case '/': 36 | response.writeHead(200, {'Content-Type': 'text/html'}); 37 | response.end(this._simpleHtml); 38 | break; 39 | case '/jasmine': 40 | response.writeHead(200, {'Content-Type': 'text/html'}); 41 | response.end(this._jasmineHtml); 42 | break; 43 | case '/json': 44 | response.writeHead(200, {'Content-Type': 'application/json'}); 45 | response.end( JSON.stringify(this._json) ); 46 | break; 47 | default: 48 | break; 49 | } 50 | }; 51 | 52 | Server.prototype.start = function(){ 53 | this._httpServer.listen(this.port, "127.0.0.1"); 54 | 55 | console.log("Started http server on port ", this.port); 56 | }; 57 | 58 | var server; 59 | exports.start = function(port, callback){ 60 | server = new Server(port); 61 | server.start(function(){ 62 | callback(); 63 | }); 64 | }; 65 | exports.stop = function(callback){ 66 | if(server) server.stop(); 67 | }; 68 | exports.updateJson = function(json){ 69 | if(server) server.updateJson(json); 70 | }; 71 | exports.updateHtml = function(html){ 72 | if(server) server.updateHtml(html); 73 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-dom", 3 | "version": "1.0.0", 4 | "description": "Run your jasmine html SpecRunner in node.js.", 5 | "keywords": [ 6 | "javascript testing", 7 | "bdd", 8 | "jasmine", 9 | "testing", 10 | "test-automation" 11 | ], 12 | "homepage": "https://github.com/andrewpmckenzie/node-jasmine-dom", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/andrewpmckenzie/node-jasmine-dom.git" 16 | }, 17 | "author": "Andrew McKenzie (http://andrew.mckenzie.name)", 18 | "thanks-to": "jasmine-node", 19 | "engines": { 20 | "node": ">= 0.10.38" 21 | }, 22 | "dependencies": { 23 | "cssom": "0.2.3", 24 | "htmlparser": "1.7.4", 25 | "jsdom": "1.5.0", 26 | "lodash": "^3.10.1", 27 | "node-static": "0.5.9", 28 | "request": "2.9.153", 29 | "yaml": "0.2.3" 30 | }, 31 | "bin": "bin/jasmine-dom", 32 | "main": "lib/jasmine-dom", 33 | "scripts": { 34 | "test": "./node_modules/.bin/mocha test" 35 | }, 36 | "devDependencies": { 37 | "chai": "^3.4.1", 38 | "mocha": "^2.3.4", 39 | "mockery": "^1.4.0", 40 | "sinon": "^1.17.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var assert = require('chai').assert; 3 | var path = require('path'); 4 | var sinon = require('sinon'); 5 | var util = require('util'); 6 | 7 | var examplesDir = path.normalize(path.join(__dirname, '..', 'examples')); 8 | 9 | /** 10 | * The report contains Class instances. toJson() leaves objects that can be 11 | * cleanly compared with deepEqual. 12 | */ 13 | var toJson = function (o) { 14 | var asString = JSON.stringify(o); 15 | return JSON.parse(asString); 16 | }; 17 | 18 | describe('node-jasmine-dom', function () { 19 | 20 | describe('JasmineRunner', function () { 21 | 22 | var jasmineDom = require('../lib/jasmine-dom'); 23 | 24 | it('runs a passing spec from an html file', function (done) { 25 | var filePath = path.normalize(path.join(__dirname, '..', 'examples', 'runner-pass.html')); 26 | 27 | jasmineDom.run({ 28 | runners: [ filePath ], 29 | onDone: function (report) { 30 | assert.deepEqual(report.simple, { 31 | details: [ 32 | { 33 | name: filePath, 34 | passed: 2, failed: 0, total: 2 35 | } 36 | ], 37 | passed: 2, failed: 0, total: 2, suites: 1, failureDetails: [ ], status: 'Passed' 38 | }); 39 | 40 | assert.deepEqual(report.detailed, { 41 | details: [ 42 | { 43 | name: filePath, 44 | failureDetails: { }, 45 | passes: [ 46 | 'Should add two numbers', 47 | 'Should divide two numbers' 48 | ], 49 | failures: [ ], 50 | suites: [ 51 | 'Example functions that update the DOM', 52 | 'Example functions (should pass)' 53 | ], 54 | passed: 2, 55 | failed: 0, 56 | total: 2 57 | } 58 | ], 59 | passDetails: [ 60 | 'Should add two numbers', 61 | 'Should divide two numbers' 62 | ], 63 | passed: 2, failed: 0, total: 2, suites: 1, failureDetails: [ ], status: 'Passed' 64 | }); 65 | 66 | assert.equal( 67 | report.junit, 68 | util.format( 69 | '' + 70 | '' + 71 | '' + 72 | '', 73 | filePath, 74 | filePath 75 | ) 76 | ); 77 | 78 | done(); 79 | } 80 | }); 81 | }); 82 | 83 | it('runs a failing spec from an html file', function (done) { 84 | var fileDir = path.normalize(path.join(__dirname, '..', 'examples')); 85 | var filePath = path.join(fileDir, 'runner-fail.html'); 86 | jasmineDom.run({ 87 | runners: [ filePath ], 88 | onDone: function (report) { 89 | 90 | assert.deepEqual(toJson(report.simple), { 91 | details: [ 92 | { 93 | name: filePath, 94 | failures: [ 95 | { 96 | type: 'expect', 97 | matcherName: 'toEqual', 98 | passed_: false, 99 | expected: 8, 100 | actual: 3, 101 | message: 'Expected 3 to equal 8.', 102 | trace: { }, 103 | suite: 'Example functions (should fail)', 104 | spec: 'Should fail!!', 105 | group: filePath 106 | } 107 | ], 108 | passed: 1, failed: 1, total: 2 109 | } 110 | ], 111 | failureDetails: [ 112 | { 113 | type: 'expect', 114 | matcherName: 'toEqual', 115 | passed_: false, 116 | expected: 8, 117 | actual: 3, 118 | message: 'Expected 3 to equal 8.', 119 | trace: { }, 120 | suite: 'Example functions (should fail)', 121 | spec: 'Should fail!!', 122 | group: filePath 123 | } 124 | ], 125 | passed: 1, failed: 1, total: 2, suites: 1, 126 | status: 'Failed' 127 | }); 128 | 129 | assert.deepEqual(toJson(report.detailed), { 130 | details: [ 131 | { 132 | name: filePath, 133 | failureDetails: { 134 | 'Should fail!!': [ 135 | { 136 | type: 'expect', 137 | matcherName: 'toEqual', 138 | passed_: false, 139 | expected: 8, 140 | actual: 3, 141 | message: 'Expected 3 to equal 8.', 142 | trace: { }, 143 | suite: 'Example functions (should fail)', 144 | spec: 'Should fail!!', 145 | group: filePath 146 | } 147 | ] 148 | }, 149 | passes: [ 'Should multiply two numbers' ], 150 | failures: [ 'Should fail!!' ], 151 | suites: [ 'Example functions (should fail)' ], 152 | passed: 1, 153 | failed: 1, 154 | total: 2 155 | } 156 | ], 157 | passed: 1, 158 | failed: 1, 159 | total: 2, 160 | suites: 1, 161 | failureDetails: [ 'Should fail!!' ], 162 | passDetails: [ 'Should multiply two numbers' ], 163 | status: 'Failed' 164 | }); 165 | 166 | assert.equal( 167 | report.junit, 168 | '' + 169 | util.format('', filePath) + 170 | util.format('', filePath) + 171 | '' + 172 | ( 173 | ' (FILE_DIR/spec/example-fail_spec.js:11:13)\n' + 178 | ' at jasmine.Block.execute (FILE_DIR/lib/jasmine/jasmine.js:968:15)\n' + 179 | ' at jasmine.Queue.next_ (FILE_DIR/lib/jasmine/jasmine.js:1739:31)\n' + 180 | ' at jasmine.Queue.start (FILE_DIR/lib/jasmine/jasmine.js:1692:8)\n' + 181 | ' at jasmine.Spec.execute (FILE_DIR/lib/jasmine/jasmine.js:2018:14)\n' + 182 | ' at jasmine.Queue.next_ (FILE_DIR/lib/jasmine/jasmine.js:1739:31)\n' + 183 | ' at onComplete (FILE_DIR/lib/jasmine/jasmine.js:1735:18)\n' + 184 | ' at jasmine.Spec.finish (FILE_DIR/lib/jasmine/jasmine.js:1992:5)' + 185 | ']]>' 186 | ).replace(/FILE_DIR/g, util.format('file://%s', fileDir)) + 187 | '' + 188 | '' + 189 | '' 190 | ); 191 | 192 | done(); 193 | } 194 | }); 195 | }); 196 | 197 | }); 198 | 199 | describe('args2options', function () { 200 | 201 | var args2options = require('../lib/args2options'); 202 | 203 | it('parses a config.yml file provided with the `--config` flag', function () { 204 | var configPath = path.join(examplesDir, 'config.yaml'); 205 | 206 | var options = args2options([ '--config', configPath ]); 207 | assert.deepEqual(options.runners, [ 208 | { 209 | name: 'A suite that passes', 210 | runner: util.format('%s/runner-pass.html', examplesDir) 211 | }, 212 | { 213 | name: 'A suite that fails', 214 | runner: util.format('%s/runner-fail.html', examplesDir) 215 | } 216 | ]); 217 | }); 218 | 219 | it('parses a runner file provided with the `--runner` flag', function () { 220 | var absoluteRunnerPath = path.join(examplesDir, '/a-runner.html'); 221 | var options = args2options([ '--runner', './examples/a-runner.html' ]); 222 | assert.deepEqual(options.runners, [ absoluteRunnerPath ]) 223 | }); 224 | 225 | it('prints results to the console with the `--format nice` flag', function () { 226 | var runnerPath = path.join(examplesDir, 'ignore-me.html'); 227 | var options = args2options([ '--format', 'nice', '--runner', runnerPath ]); 228 | 229 | var formatter = options.onDone; 230 | assert.equal(typeof formatter, 'function'); 231 | 232 | sinon.stub(process, 'exit'); 233 | sinon.stub(console, 'log'); 234 | sinon.stub(console, 'error'); 235 | 236 | formatter({ 237 | simple: { 238 | details: [ 239 | { 240 | name: runnerPath, 241 | failures: [ 242 | { 243 | type: 'expect', matcherName: 'toEqual', message: 'Expected 3 to equal 8.', 244 | passed_: false, expected: 8, actual: 3, 245 | trace: { }, 246 | suite: 'Example functions (should fail)', spec: 'Should fail!!', group: runnerPath 247 | } 248 | ], 249 | passed: 1, failed: 1, total: 2 250 | } 251 | ], 252 | failureDetails: [ 253 | { 254 | type: 'expect', matcherName: 'toEqual', message: 'Expected 3 to equal 8.', 255 | passed_: false, expected: 8, actual: 3, 256 | trace: { }, 257 | suite: 'Example functions (should fail)', spec: 'Should fail!!', group: runnerPath 258 | } 259 | ], 260 | passed: 1, failed: 1, total: 2, suites: 1, 261 | status: 'Failed' 262 | } 263 | }); 264 | 265 | var consoleLogOutput = _.flatten(_.map(console.log.getCalls(), function (call) { return call.args[0].split('\n'); })); 266 | 267 | process.exit.restore(); 268 | console.log.restore(); 269 | console.error.restore(); 270 | 271 | assert.deepEqual(consoleLogOutput, [ 272 | '====== FAILED ====== ', 273 | util.format(' - In %s >> Example functions (should fail) >> Should fail!! :: Expected 3 to equal 8.', runnerPath), 274 | '' 275 | ]); 276 | 277 | }); 278 | 279 | it('prints results to the console with the `--format detailed` flag', function () { 280 | var runnerPath = path.join(examplesDir, 'ignore-me.html'); 281 | var options = args2options([ '--format', 'detailed', '--runner', runnerPath ]); 282 | 283 | var formatter = options.onDone; 284 | assert.equal(typeof formatter, 'function'); 285 | 286 | sinon.stub(process, 'exit'); 287 | sinon.stub(console, 'log'); 288 | sinon.stub(console, 'error'); 289 | 290 | formatter({ 291 | detailed: { 292 | details: [ 293 | { 294 | name: runnerPath, 295 | failureDetails: { 296 | 'Should fail!!': [ 297 | { 298 | type: 'expect', matcherName: 'toEqual', message: 'Expected 3 to equal 8.', 299 | passed_: false, expected: 8, actual: 3, 300 | trace: { }, 301 | suite: 'Example functions (should fail)', spec: 'Should fail!!', group: runnerPath 302 | } 303 | ] 304 | }, 305 | passes: [ 'Should multiply two numbers' ], 306 | failures: [ 'Should fail!!' ], 307 | suites: [ 'Example functions (should fail)' ], 308 | passed: 1, failed: 1, total: 2 309 | } 310 | ], 311 | passed: 1, failed: 1, total: 2, suites: 1, 312 | failureDetails: [ 'Should fail!!' ], passDetails: [ 'Should multiply two numbers' ], 313 | status: 'Failed' 314 | } 315 | }); 316 | 317 | var consoleLogOutput = _.flatten(_.map(console.log.getCalls(), function (call) { return call.args[0].split('\n'); })); 318 | 319 | process.exit.restore(); 320 | console.log.restore(); 321 | console.error.restore(); 322 | 323 | assert.deepEqual(consoleLogOutput, [ 324 | '', 325 | '====== FAILED ====== ', 326 | '', 327 | 'Example functions (should fail) - 2 tests ', 328 | ' PASSES ', 329 | ' - Should multiply two numbers ', 330 | ' FAILURES ', 331 | ' - Should fail!! ', 332 | ' [Expected 3 to equal 8.] ', 333 | '', 334 | '====== FAILED ====== ', 335 | '', 336 | '' 337 | ]); 338 | 339 | }); 340 | }); 341 | 342 | }); 343 | --------------------------------------------------------------------------------