1 | /* |
---|
2 | Copyright (c) 2008-2013 Pivotal Labs |
---|
3 | |
---|
4 | Permission is hereby granted, free of charge, to any person obtaining |
---|
5 | a copy of this software and associated documentation files (the |
---|
6 | "Software"), to deal in the Software without restriction, including |
---|
7 | without limitation the rights to use, copy, modify, merge, publish, |
---|
8 | distribute, sublicense, and/or sell copies of the Software, and to |
---|
9 | permit persons to whom the Software is furnished to do so, subject to |
---|
10 | the following conditions: |
---|
11 | |
---|
12 | The above copyright notice and this permission notice shall be |
---|
13 | included in all copies or substantial portions of the Software. |
---|
14 | |
---|
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
---|
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
---|
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
---|
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
---|
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
---|
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
---|
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
---|
22 | */ |
---|
23 | jasmineRequire.html = function(j$) { |
---|
24 | j$.ResultsNode = jasmineRequire.ResultsNode(); |
---|
25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); |
---|
26 | j$.QueryString = jasmineRequire.QueryString(); |
---|
27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); |
---|
28 | }; |
---|
29 | |
---|
30 | jasmineRequire.HtmlReporter = function(j$) { |
---|
31 | |
---|
32 | var noopTimer = { |
---|
33 | start: function() {}, |
---|
34 | elapsed: function() { return 0; } |
---|
35 | }; |
---|
36 | |
---|
37 | function HtmlReporter(options) { |
---|
38 | var env = options.env || {}, |
---|
39 | getContainer = options.getContainer, |
---|
40 | createElement = options.createElement, |
---|
41 | createTextNode = options.createTextNode, |
---|
42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, |
---|
43 | timer = options.timer || noopTimer, |
---|
44 | results = [], |
---|
45 | specsExecuted = 0, |
---|
46 | failureCount = 0, |
---|
47 | pendingSpecCount = 0, |
---|
48 | htmlReporterMain, |
---|
49 | symbols; |
---|
50 | |
---|
51 | this.initialize = function() { |
---|
52 | htmlReporterMain = createDom("div", {className: "html-reporter"}, |
---|
53 | createDom("div", {className: "banner"}, |
---|
54 | createDom("span", {className: "title"}, "Jasmine"), |
---|
55 | createDom("span", {className: "version"}, j$.version) |
---|
56 | ), |
---|
57 | createDom("ul", {className: "symbol-summary"}), |
---|
58 | createDom("div", {className: "alert"}), |
---|
59 | createDom("div", {className: "results"}, |
---|
60 | createDom("div", {className: "failures"}) |
---|
61 | ) |
---|
62 | ); |
---|
63 | getContainer().appendChild(htmlReporterMain); |
---|
64 | |
---|
65 | symbols = find(".symbol-summary"); |
---|
66 | }; |
---|
67 | |
---|
68 | var totalSpecsDefined; |
---|
69 | this.jasmineStarted = function(options) { |
---|
70 | totalSpecsDefined = options.totalSpecsDefined || 0; |
---|
71 | timer.start(); |
---|
72 | }; |
---|
73 | |
---|
74 | var summary = createDom("div", {className: "summary"}); |
---|
75 | |
---|
76 | var topResults = new j$.ResultsNode({}, "", null), |
---|
77 | currentParent = topResults; |
---|
78 | |
---|
79 | this.suiteStarted = function(result) { |
---|
80 | currentParent.addChild(result, "suite"); |
---|
81 | currentParent = currentParent.last(); |
---|
82 | }; |
---|
83 | |
---|
84 | this.suiteDone = function(result) { |
---|
85 | if (currentParent == topResults) { |
---|
86 | return; |
---|
87 | } |
---|
88 | |
---|
89 | currentParent = currentParent.parent; |
---|
90 | }; |
---|
91 | |
---|
92 | this.specStarted = function(result) { |
---|
93 | currentParent.addChild(result, "spec"); |
---|
94 | }; |
---|
95 | |
---|
96 | var failures = []; |
---|
97 | this.specDone = function(result) { |
---|
98 | if (result.status != "disabled") { |
---|
99 | specsExecuted++; |
---|
100 | } |
---|
101 | |
---|
102 | symbols.appendChild(createDom("li", { |
---|
103 | className: result.status, |
---|
104 | id: "spec_" + result.id, |
---|
105 | title: result.fullName |
---|
106 | } |
---|
107 | )); |
---|
108 | |
---|
109 | if (result.status == "failed") { |
---|
110 | failureCount++; |
---|
111 | |
---|
112 | var failure = |
---|
113 | createDom("div", {className: "spec-detail failed"}, |
---|
114 | createDom("div", {className: "description"}, |
---|
115 | createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName) |
---|
116 | ), |
---|
117 | createDom("div", {className: "messages"}) |
---|
118 | ); |
---|
119 | var messages = failure.childNodes[1]; |
---|
120 | |
---|
121 | for (var i = 0; i < result.failedExpectations.length; i++) { |
---|
122 | var expectation = result.failedExpectations[i]; |
---|
123 | messages.appendChild(createDom("div", {className: "result-message"}, expectation.message)); |
---|
124 | messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack)); |
---|
125 | } |
---|
126 | |
---|
127 | failures.push(failure); |
---|
128 | } |
---|
129 | |
---|
130 | if (result.status == "pending") { |
---|
131 | pendingSpecCount++; |
---|
132 | } |
---|
133 | }; |
---|
134 | |
---|
135 | this.jasmineDone = function() { |
---|
136 | var banner = find(".banner"); |
---|
137 | banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s")); |
---|
138 | |
---|
139 | var alert = find(".alert"); |
---|
140 | |
---|
141 | alert.appendChild(createDom("span", { className: "exceptions" }, |
---|
142 | createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"), |
---|
143 | createDom("input", { |
---|
144 | className: "raise", |
---|
145 | id: "raise-exceptions", |
---|
146 | type: "checkbox" |
---|
147 | }) |
---|
148 | )); |
---|
149 | var checkbox = find("input"); |
---|
150 | |
---|
151 | checkbox.checked = !env.catchingExceptions(); |
---|
152 | checkbox.onclick = onRaiseExceptionsClick; |
---|
153 | |
---|
154 | if (specsExecuted < totalSpecsDefined) { |
---|
155 | var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all"; |
---|
156 | alert.appendChild( |
---|
157 | createDom("span", {className: "bar skipped"}, |
---|
158 | createDom("a", {href: "?", title: "Run all specs"}, skippedMessage) |
---|
159 | ) |
---|
160 | ); |
---|
161 | } |
---|
162 | var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount); |
---|
163 | if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); } |
---|
164 | |
---|
165 | var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed"); |
---|
166 | alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage)); |
---|
167 | |
---|
168 | var results = find(".results"); |
---|
169 | results.appendChild(summary); |
---|
170 | |
---|
171 | summaryList(topResults, summary); |
---|
172 | |
---|
173 | function summaryList(resultsTree, domParent) { |
---|
174 | var specListNode; |
---|
175 | for (var i = 0; i < resultsTree.children.length; i++) { |
---|
176 | var resultNode = resultsTree.children[i]; |
---|
177 | if (resultNode.type == "suite") { |
---|
178 | var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id}, |
---|
179 | createDom("li", {className: "suite-detail"}, |
---|
180 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) |
---|
181 | ) |
---|
182 | ); |
---|
183 | |
---|
184 | summaryList(resultNode, suiteListNode); |
---|
185 | domParent.appendChild(suiteListNode); |
---|
186 | } |
---|
187 | if (resultNode.type == "spec") { |
---|
188 | if (domParent.getAttribute("class") != "specs") { |
---|
189 | specListNode = createDom("ul", {className: "specs"}); |
---|
190 | domParent.appendChild(specListNode); |
---|
191 | } |
---|
192 | specListNode.appendChild( |
---|
193 | createDom("li", { |
---|
194 | className: resultNode.result.status, |
---|
195 | id: "spec-" + resultNode.result.id |
---|
196 | }, |
---|
197 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) |
---|
198 | ) |
---|
199 | ); |
---|
200 | } |
---|
201 | } |
---|
202 | } |
---|
203 | |
---|
204 | if (failures.length) { |
---|
205 | alert.appendChild( |
---|
206 | createDom('span', {className: "menu bar spec-list"}, |
---|
207 | createDom("span", {}, "Spec List | "), |
---|
208 | createDom('a', {className: "failures-menu", href: "#"}, "Failures"))); |
---|
209 | alert.appendChild( |
---|
210 | createDom('span', {className: "menu bar failure-list"}, |
---|
211 | createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"), |
---|
212 | createDom("span", {}, " | Failures "))); |
---|
213 | |
---|
214 | find(".failures-menu").onclick = function() { |
---|
215 | setMenuModeTo('failure-list'); |
---|
216 | }; |
---|
217 | find(".spec-list-menu").onclick = function() { |
---|
218 | setMenuModeTo('spec-list'); |
---|
219 | }; |
---|
220 | |
---|
221 | setMenuModeTo('failure-list'); |
---|
222 | |
---|
223 | var failureNode = find(".failures"); |
---|
224 | for (var i = 0; i < failures.length; i++) { |
---|
225 | failureNode.appendChild(failures[i]); |
---|
226 | } |
---|
227 | } |
---|
228 | }; |
---|
229 | |
---|
230 | return this; |
---|
231 | |
---|
232 | function find(selector) { |
---|
233 | return getContainer().querySelector(selector); |
---|
234 | } |
---|
235 | |
---|
236 | function createDom(type, attrs, childrenVarArgs) { |
---|
237 | var el = createElement(type); |
---|
238 | |
---|
239 | for (var i = 2; i < arguments.length; i++) { |
---|
240 | var child = arguments[i]; |
---|
241 | |
---|
242 | if (typeof child === 'string') { |
---|
243 | el.appendChild(createTextNode(child)); |
---|
244 | } else { |
---|
245 | if (child) { |
---|
246 | el.appendChild(child); |
---|
247 | } |
---|
248 | } |
---|
249 | } |
---|
250 | |
---|
251 | for (var attr in attrs) { |
---|
252 | if (attr == "className") { |
---|
253 | el[attr] = attrs[attr]; |
---|
254 | } else { |
---|
255 | el.setAttribute(attr, attrs[attr]); |
---|
256 | } |
---|
257 | } |
---|
258 | |
---|
259 | return el; |
---|
260 | } |
---|
261 | |
---|
262 | function pluralize(singular, count) { |
---|
263 | var word = (count == 1 ? singular : singular + "s"); |
---|
264 | |
---|
265 | return "" + count + " " + word; |
---|
266 | } |
---|
267 | |
---|
268 | function specHref(result) { |
---|
269 | return "?spec=" + encodeURIComponent(result.fullName); |
---|
270 | } |
---|
271 | |
---|
272 | function setMenuModeTo(mode) { |
---|
273 | htmlReporterMain.setAttribute("class", "html-reporter " + mode); |
---|
274 | } |
---|
275 | } |
---|
276 | |
---|
277 | return HtmlReporter; |
---|
278 | }; |
---|
279 | |
---|
280 | jasmineRequire.HtmlSpecFilter = function() { |
---|
281 | function HtmlSpecFilter(options) { |
---|
282 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); |
---|
283 | var filterPattern = new RegExp(filterString); |
---|
284 | |
---|
285 | this.matches = function(specName) { |
---|
286 | return filterPattern.test(specName); |
---|
287 | }; |
---|
288 | } |
---|
289 | |
---|
290 | return HtmlSpecFilter; |
---|
291 | }; |
---|
292 | |
---|
293 | jasmineRequire.ResultsNode = function() { |
---|
294 | function ResultsNode(result, type, parent) { |
---|
295 | this.result = result; |
---|
296 | this.type = type; |
---|
297 | this.parent = parent; |
---|
298 | |
---|
299 | this.children = []; |
---|
300 | |
---|
301 | this.addChild = function(result, type) { |
---|
302 | this.children.push(new ResultsNode(result, type, this)); |
---|
303 | }; |
---|
304 | |
---|
305 | this.last = function() { |
---|
306 | return this.children[this.children.length - 1]; |
---|
307 | }; |
---|
308 | } |
---|
309 | |
---|
310 | return ResultsNode; |
---|
311 | }; |
---|
312 | |
---|
313 | jasmineRequire.QueryString = function() { |
---|
314 | function QueryString(options) { |
---|
315 | |
---|
316 | this.setParam = function(key, value) { |
---|
317 | var paramMap = queryStringToParamMap(); |
---|
318 | paramMap[key] = value; |
---|
319 | options.getWindowLocation().search = toQueryString(paramMap); |
---|
320 | }; |
---|
321 | |
---|
322 | this.getParam = function(key) { |
---|
323 | return queryStringToParamMap()[key]; |
---|
324 | }; |
---|
325 | |
---|
326 | return this; |
---|
327 | |
---|
328 | function toQueryString(paramMap) { |
---|
329 | var qStrPairs = []; |
---|
330 | for (var prop in paramMap) { |
---|
331 | qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop])); |
---|
332 | } |
---|
333 | return "?" + qStrPairs.join('&'); |
---|
334 | } |
---|
335 | |
---|
336 | function queryStringToParamMap() { |
---|
337 | var paramStr = options.getWindowLocation().search.substring(1), |
---|
338 | params = [], |
---|
339 | paramMap = {}; |
---|
340 | |
---|
341 | if (paramStr.length > 0) { |
---|
342 | params = paramStr.split('&'); |
---|
343 | for (var i = 0; i < params.length; i++) { |
---|
344 | var p = params[i].split('='); |
---|
345 | var value = decodeURIComponent(p[1]); |
---|
346 | if (value === "true" || value === "false") { |
---|
347 | value = JSON.parse(value); |
---|
348 | } |
---|
349 | paramMap[decodeURIComponent(p[0])] = value; |
---|
350 | } |
---|
351 | } |
---|
352 | |
---|
353 | return paramMap; |
---|
354 | } |
---|
355 | |
---|
356 | } |
---|
357 | |
---|
358 | return QueryString; |
---|
359 | }; |
---|