UNPKG

6.65 kBJavaScriptView Raw
1(function(global) {
2 var UNDEFINED,
3 exportObject;
4
5 if (typeof module !== "undefined" && module.exports) {
6 exportObject = exports;
7 } else {
8 exportObject = global.jasmineReporters = global.jasmineReporters || {};
9 }
10
11 function isFailed(obj) { return obj.status === "failed"; }
12 function isSkipped(obj) { return obj.status === "pending"; }
13 function isDisabled(obj) { return obj.status === "disabled"; }
14 function pad(n) { return n < 10 ? "0"+n : n; }
15 function padThree(n) { return n < 10 ? "00"+n : n < 100 ? "0"+n : n; }
16 function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe`
17 for (var prop in obj) {
18 if (obj.hasOwnProperty(prop)) {
19 dupe[prop] = obj[prop];
20 }
21 }
22 return dupe;
23 }
24 function ISODateString(d) {
25 return d.getUTCFullYear() + "-" +
26 pad(d.getUTCMonth()+1) + "-" +
27 pad(d.getUTCDate()) + "T" +
28 pad(d.getUTCHours()) + ":" +
29 pad(d.getUTCMinutes()) + ":" +
30 pad(d.getUTCSeconds()) + "." +
31 // TeamCity wants ss.SSS
32 padThree(d.getUTCMilliseconds());
33 }
34 function log(str) {
35 var con = global.console || console;
36 if (con && con.log) {
37 con.log(str);
38 }
39 }
40
41
42 /**
43 * Basic reporter that outputs spec results for the TeamCity build system
44 *
45 * Usage:
46 *
47 * jasmine.getEnv().addReporter(new jasmineReporters.TeamCityReporter());
48 */
49 exportObject.TeamCityReporter = function(options) {
50 options = options || {};
51 var self = this;
52 self.started = false;
53 self.finished = false;
54
55 if(options.modifySuiteName && typeof options.modifySuiteName !== "function") {
56 throw new Error('option "modifySuiteName" must be a function');
57 }
58
59 var delegates = {};
60 delegates.modifySuiteName = options.modifySuiteName;
61
62 var currentSuite = null,
63 // when use use fit, jasmine never calls suiteStarted / suiteDone, so make a fake one to use
64 fakeFocusedSuite = {
65 id: "focused",
66 description: "focused specs",
67 fullName: "focused specs"
68 };
69
70 var __suites = {}, __specs = {};
71 function getSuite(suite) {
72 __suites[suite.id] = extend(__suites[suite.id] || {}, suite);
73 return __suites[suite.id];
74 }
75 function getSpec(spec) {
76 __specs[spec.id] = extend(__specs[spec.id] || {}, spec);
77 return __specs[spec.id];
78 }
79
80 self.jasmineStarted = function() {
81 exportObject.startTime = new Date();
82 self.started = true;
83 tclog("progressStart 'Running Jasmine Tests'");
84 };
85 self.suiteStarted = function(suite) {
86 suite = getSuite(suite);
87 suite._parent = currentSuite;
88 currentSuite = suite;
89 tclog("testSuiteStarted", {
90 name: suite.description
91 });
92 };
93 self.specStarted = function(spec) {
94 if (!currentSuite) {
95 // focused spec (fit) -- suiteStarted was never called
96 self.suiteStarted(fakeFocusedSuite);
97 }
98 spec = getSpec(spec);
99 tclog("testStarted", {
100 name: spec.description,
101 captureStandardOutput: "true"
102 });
103 };
104 self.specDone = function(spec) {
105 spec = getSpec(spec);
106 if (isSkipped(spec) || isDisabled(spec)) {
107 tclog("testIgnored", {
108 name: spec.description
109 });
110 }
111 // TeamCity specifies there should only be a single `testFailed`
112 // message, so we'll only grab the first failedExpectation
113 if (isFailed(spec) && spec.failedExpectations.length) {
114 var failure = spec.failedExpectations[0];
115 tclog("testFailed", {
116 name: spec.description,
117 message: failure.message,
118 details: failure.stack
119 });
120 }
121 tclog("testFinished", {
122 name: spec.description
123 });
124 };
125 self.suiteDone = function(suite) {
126 suite = getSuite(suite);
127 if (suite._parent === UNDEFINED) {
128 // disabled suite (xdescribe) -- suiteStarted was never called
129 self.suiteStarted(suite);
130 }
131 tclog("testSuiteFinished", {
132 name: suite.description
133 });
134 currentSuite = suite._parent;
135 };
136 self.jasmineDone = function() {
137 if (currentSuite) {
138 // focused spec (fit) -- suiteDone was never called
139 self.suiteDone(fakeFocusedSuite);
140 }
141 tclog("progressFinish 'Running Jasmine Tests'");
142
143 self.finished = true;
144 // this is so phantomjs-testrunner.js can tell if we're done executing
145 exportObject.endTime = new Date();
146 };
147
148 // shorthand for logging TeamCity messages
149 // defined here because it needs access to the `delegates` closure variable
150 function tclog(message, attrs) {
151 var str = "##teamcity[" + message;
152 if (typeof(attrs) === "object") {
153 if (!("timestamp" in attrs)) {
154 attrs.timestamp = new Date();
155 }
156 for (var prop in attrs) {
157 if (attrs.hasOwnProperty(prop)) {
158 if(delegates.modifySuiteName && message.indexOf("testSuite") === 0 && prop === "name") {
159 attrs[prop] = delegates.modifySuiteName(attrs[prop]);
160 }
161 str += " " + prop + "='" + escapeTeamCityString(attrs[prop]) + "'";
162 }
163 }
164 }
165 str += "]";
166 log(str);
167 }
168 };
169
170 function escapeTeamCityString(str) {
171 if(!str) {
172 return "";
173 }
174 if (Object.prototype.toString.call(str) === "[object Date]") {
175 return ISODateString(str);
176 }
177
178 return str.replace(/\|/g, "||")
179 .replace(/'/g, "|'")
180 .replace(/\n/g, "|n")
181 .replace(/\r/g, "|r")
182 .replace(/\u0085/g, "|x")
183 .replace(/\u2028/g, "|l")
184 .replace(/\u2029/g, "|p")
185 .replace(/\[/g, "|[")
186 .replace(/]/g, "|]");
187 }
188})(this);