1 | var utils = require('./utils');
|
2 | var defaultDepth = require('./defaultDepth');
|
3 | var useFullStackTrace = require('./useFullStackTrace');
|
4 |
|
5 | var errorMethodBlacklist = [
|
6 | 'message',
|
7 | 'line',
|
8 | 'sourceId',
|
9 | 'sourceURL',
|
10 | 'stack',
|
11 | 'stackArray'
|
12 | ].reduce(function (result, prop) {
|
13 | result[prop] = true;
|
14 | return result;
|
15 | }, {});
|
16 |
|
17 | function UnexpectedError(expect, parent) {
|
18 | this.errorMode = (expect && expect.errorMode) || 'default';
|
19 | var base = Error.call(this, '');
|
20 |
|
21 | if (Error.captureStackTrace) {
|
22 | Error.captureStackTrace(this, UnexpectedError);
|
23 | } else {
|
24 |
|
25 | try {
|
26 | throw base;
|
27 | } catch (err) {}
|
28 | this.stack = base.stack;
|
29 | }
|
30 |
|
31 |
|
32 |
|
33 | Object.defineProperties(this, {
|
34 | expect: {
|
35 | enumerable: false,
|
36 | value: expect
|
37 | }
|
38 | });
|
39 | this.parent = parent || null;
|
40 | this.name = 'UnexpectedError';
|
41 | }
|
42 |
|
43 | UnexpectedError.prototype = Object.create(Error.prototype);
|
44 |
|
45 | UnexpectedError.prototype.useFullStackTrace = useFullStackTrace;
|
46 |
|
47 | var missingOutputMessage =
|
48 | 'You must either provide a format or a magicpen instance';
|
49 | UnexpectedError.prototype.outputFromOptions = function(options) {
|
50 | if (!options) {
|
51 | throw new Error(missingOutputMessage);
|
52 | }
|
53 |
|
54 | if (typeof options === 'string') {
|
55 | return this.expect.createOutput(options);
|
56 | }
|
57 |
|
58 | if (options.isMagicPen) {
|
59 | return options.clone();
|
60 | }
|
61 |
|
62 | if (options.output) {
|
63 | return options.output.clone();
|
64 | }
|
65 |
|
66 | if (options.format) {
|
67 | return this.expect.createOutput(options.format);
|
68 | }
|
69 |
|
70 | throw new Error(missingOutputMessage);
|
71 | };
|
72 |
|
73 | UnexpectedError.prototype._isUnexpected = true;
|
74 | UnexpectedError.prototype.isUnexpected = true;
|
75 | UnexpectedError.prototype.buildDiff = function(options) {
|
76 | var output = this.outputFromOptions(options);
|
77 | var expect = this.expect;
|
78 | return (
|
79 | this.createDiff &&
|
80 | this.createDiff(
|
81 | output,
|
82 | function (actual, expected) { return expect.diff(actual, expected, output.clone()); },
|
83 | function (v, depth) { return output.clone().appendInspected(v, (depth || defaultDepth) - 1); },
|
84 | function (actual, expected) { return expect.equal(actual, expected); }
|
85 | )
|
86 | );
|
87 | };
|
88 |
|
89 | UnexpectedError.prototype.getDefaultErrorMessage = function(options) {
|
90 | var output = this.outputFromOptions(options);
|
91 | if (this.expect.testDescription) {
|
92 | output.append(this.expect.standardErrorMessage(output.clone(), options));
|
93 | } else if (typeof this.output === 'function') {
|
94 | this.output.call(output, output);
|
95 | }
|
96 |
|
97 | var errorWithDiff = this;
|
98 | while (!errorWithDiff.createDiff && errorWithDiff.parent) {
|
99 | errorWithDiff = errorWithDiff.parent;
|
100 | }
|
101 |
|
102 | if (errorWithDiff && errorWithDiff.createDiff) {
|
103 | var comparison = errorWithDiff.buildDiff(options);
|
104 | if (comparison) {
|
105 | output.nl(2).append(comparison);
|
106 | }
|
107 | }
|
108 |
|
109 | return output;
|
110 | };
|
111 |
|
112 | UnexpectedError.prototype.getNestedErrorMessage = function(options) {
|
113 | var output = this.outputFromOptions(options);
|
114 | if (this.expect.testDescription) {
|
115 | output.append(this.expect.standardErrorMessage(output.clone(), options));
|
116 | } else if (typeof this.output === 'function') {
|
117 | this.output.call(output, output);
|
118 | }
|
119 |
|
120 | var parent = this.parent;
|
121 | while (parent.getErrorMode() === 'bubble') {
|
122 | parent = parent.parent;
|
123 | }
|
124 |
|
125 | if (typeof options === 'string') {
|
126 | options = { format: options };
|
127 | } else if (options && options.isMagicPen) {
|
128 | options = { output: options };
|
129 | }
|
130 |
|
131 | output
|
132 | .nl()
|
133 | .indentLines()
|
134 | .i()
|
135 | .block(
|
136 | parent.getErrorMessage(
|
137 | utils.extend({}, options || {}, {
|
138 | compact: this.expect.subject === parent.expect.subject
|
139 | })
|
140 | )
|
141 | );
|
142 | return output;
|
143 | };
|
144 |
|
145 | UnexpectedError.prototype.getDefaultOrNestedMessage = function(options) {
|
146 | if (this.hasDiff()) {
|
147 | return this.getDefaultErrorMessage(options);
|
148 | } else {
|
149 | return this.getNestedErrorMessage(options);
|
150 | }
|
151 | };
|
152 |
|
153 | UnexpectedError.prototype.hasDiff = function() {
|
154 | return !!this.getDiffMethod();
|
155 | };
|
156 |
|
157 | UnexpectedError.prototype.getDiffMethod = function() {
|
158 | var errorWithDiff = this;
|
159 | while (!errorWithDiff.createDiff && errorWithDiff.parent) {
|
160 | errorWithDiff = errorWithDiff.parent;
|
161 | }
|
162 |
|
163 | return (errorWithDiff && errorWithDiff.createDiff) || null;
|
164 | };
|
165 |
|
166 | UnexpectedError.prototype.getDiff = function(options) {
|
167 | var errorWithDiff = this;
|
168 | while (!errorWithDiff.createDiff && errorWithDiff.parent) {
|
169 | errorWithDiff = errorWithDiff.parent;
|
170 | }
|
171 |
|
172 | return errorWithDiff && errorWithDiff.buildDiff(options);
|
173 | };
|
174 |
|
175 | UnexpectedError.prototype.getDiffMessage = function(options) {
|
176 | var output = this.outputFromOptions(options);
|
177 | var comparison = this.getDiff(options);
|
178 | if (comparison) {
|
179 | output.append(comparison);
|
180 | } else if (this.expect.testDescription) {
|
181 | output.append(this.expect.standardErrorMessage(output.clone(), options));
|
182 | } else if (typeof this.output === 'function') {
|
183 | this.output.call(output, output);
|
184 | }
|
185 | return output;
|
186 | };
|
187 |
|
188 | UnexpectedError.prototype.getErrorMode = function() {
|
189 | if (!this.parent) {
|
190 | switch (this.errorMode) {
|
191 | case 'default':
|
192 | case 'bubbleThrough':
|
193 | return this.errorMode;
|
194 | default:
|
195 | return 'default';
|
196 | }
|
197 | } else {
|
198 | return this.errorMode;
|
199 | }
|
200 | };
|
201 |
|
202 | UnexpectedError.prototype.getErrorMessage = function(options) {
|
203 |
|
204 |
|
205 | var errorWithBubbleThrough = this.parent;
|
206 | while (
|
207 | errorWithBubbleThrough &&
|
208 | errorWithBubbleThrough.getErrorMode() !== 'bubbleThrough'
|
209 | ) {
|
210 | errorWithBubbleThrough = errorWithBubbleThrough.parent;
|
211 | }
|
212 | if (errorWithBubbleThrough) {
|
213 | return errorWithBubbleThrough.getErrorMessage(options);
|
214 | }
|
215 |
|
216 | var errorMode = this.getErrorMode();
|
217 | switch (errorMode) {
|
218 | case 'nested':
|
219 | return this.getNestedErrorMessage(options);
|
220 | case 'default':
|
221 | return this.getDefaultErrorMessage(options);
|
222 | case 'bubbleThrough':
|
223 | return this.getDefaultErrorMessage(options);
|
224 | case 'bubble':
|
225 | return this.parent.getErrorMessage(options);
|
226 | case 'diff':
|
227 | return this.getDiffMessage(options);
|
228 | case 'defaultOrNested':
|
229 | return this.getDefaultOrNestedMessage(options);
|
230 | default:
|
231 | throw new Error(("Unknown error mode: '" + errorMode + "'"));
|
232 | }
|
233 | };
|
234 |
|
235 | function findStackStart(lines) {
|
236 | for (var i = lines.length - 1; i >= 0; i -= 1) {
|
237 | if (lines[i] === '') {
|
238 | return i + 1;
|
239 | }
|
240 | }
|
241 |
|
242 | return -1;
|
243 | }
|
244 |
|
245 | UnexpectedError.prototype.serializeMessage = function(outputFormat) {
|
246 | if (!this._hasSerializedErrorMessage) {
|
247 | var htmlFormat = outputFormat === 'html';
|
248 | if (htmlFormat) {
|
249 | if (!('htmlMessage' in this)) {
|
250 | this.htmlMessage = this.getErrorMessage({ format: 'html' }).toString();
|
251 | }
|
252 | }
|
253 |
|
254 | this.message = "\n" + (this.getErrorMessage({
|
255 | format: htmlFormat ? 'text' : outputFormat
|
256 | }).toString()) + "\n";
|
257 |
|
258 | if (
|
259 | this.originalError &&
|
260 | this.originalError instanceof Error &&
|
261 | typeof this.originalError.stack === 'string'
|
262 | ) {
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | var index = this.originalError.stack.indexOf(
|
268 | this.originalError.message
|
269 | );
|
270 | if (index === -1) {
|
271 |
|
272 | this.stack = (this.message) + "\n" + (this.originalError.stack);
|
273 | } else {
|
274 | this.stack =
|
275 | this.message +
|
276 | this.originalError.stack.substr(
|
277 | index + this.originalError.message.length
|
278 | );
|
279 | }
|
280 | } else if (/^(Unexpected)?Error:?\n/.test(this.stack)) {
|
281 |
|
282 | var matchErrorName = /^(?:Unexpected)?Error:?\n/.exec(this.stack);
|
283 | if (matchErrorName) {
|
284 | this.stack = this.message + this.stack.substr(matchErrorName[0].length);
|
285 | }
|
286 | }
|
287 |
|
288 | if (this.stack && !this.useFullStackTrace) {
|
289 | var lines = this.stack.split(/\n/);
|
290 | var stackStart = findStackStart(lines);
|
291 |
|
292 | var newStack = lines.filter(
|
293 | function (line, i) { return i < stackStart ||
|
294 | (!/node_modules[/\\]unexpected(?:-[^/\\]+)?[/\\]/.test(line) &&
|
295 | !/executeExpect.*node_modules[/\\]unexpected[/\\]/.test(
|
296 | lines[i + 1]
|
297 | )); }
|
298 | );
|
299 |
|
300 | if (newStack.length !== lines.length) {
|
301 | var indentation = /^(\s*)/.exec(lines[lines.length - 1])[1];
|
302 |
|
303 | if (outputFormat === 'html') {
|
304 | newStack.push(
|
305 | (indentation + "set the query parameter full-trace=true to see the full stack trace")
|
306 | );
|
307 | } else {
|
308 | newStack.push(
|
309 | (indentation + "set UNEXPECTED_FULL_TRACE=true to see the full stack trace")
|
310 | );
|
311 | }
|
312 | }
|
313 |
|
314 | this.stack = newStack.join('\n');
|
315 | }
|
316 |
|
317 | this._hasSerializedErrorMessage = true;
|
318 | }
|
319 | };
|
320 |
|
321 | UnexpectedError.prototype.clone = function() {
|
322 | var that = this;
|
323 | var newError = new UnexpectedError(this.expect);
|
324 | Object.keys(that).forEach(function (key) {
|
325 | if (!errorMethodBlacklist[key]) {
|
326 | newError[key] = that[key];
|
327 | }
|
328 | });
|
329 | return newError;
|
330 | };
|
331 |
|
332 | UnexpectedError.prototype.getLabel = function() {
|
333 | var currentError = this;
|
334 | while (currentError && !currentError.label) {
|
335 | currentError = currentError.parent;
|
336 | }
|
337 | return (currentError && currentError.label) || null;
|
338 | };
|
339 |
|
340 | UnexpectedError.prototype.getParents = function() {
|
341 | var result = [];
|
342 | var parent = this.parent;
|
343 | while (parent) {
|
344 | result.push(parent);
|
345 | parent = parent.parent;
|
346 | }
|
347 | return result;
|
348 | };
|
349 |
|
350 | UnexpectedError.prototype.getAllErrors = function() {
|
351 | var result = this.getParents();
|
352 | result.unshift(this);
|
353 | return result;
|
354 | };
|
355 |
|
356 | if (Object.__defineGetter__) {
|
357 | Object.defineProperty(UnexpectedError.prototype, 'htmlMessage', {
|
358 | enumerable: true,
|
359 | get: function get() {
|
360 | return this.getErrorMessage({ format: 'html' }).toString();
|
361 | }
|
362 | });
|
363 | }
|
364 |
|
365 | module.exports = UnexpectedError;
|