better_errors 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,480 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= exception.class %> at <%= request_path %></title>
5
+ <style>
6
+ * {
7
+ margin:0;
8
+ padding:0;
9
+ }
10
+ body {
11
+ font-family:Verdana, sans-serif;
12
+ margin:0;
13
+ font-size:13px;
14
+ }
15
+ header {
16
+ padding:32px 32px;
17
+ border-bottom:1px solid #e64c38;
18
+ background-color: #ffe5e7;
19
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#ffe5e7), to(#ffb2b8)); /* Safari 4+, Chrome */
20
+ background-image: -webkit-linear-gradient(top, #ffe5e7, #ffb2b8); /* Chrome 10+, Safari 5.1+, iOS 5+ */
21
+ background-image: -moz-linear-gradient(top, #ffe5e7, #ffb2b8); /* Firefox 3.6-15 */
22
+ background-image: -o-linear-gradient(top, #ffe5e7, #ffb2b8); /* Opera 11.10-12.00 */
23
+ background-image: linear-gradient(to bottom, #ffe5e7, #ffb2b8); /* Firefox 16+, IE10, Opera 12.50+ */
24
+ }
25
+ h2 {
26
+ font-weight:normal;
27
+ margin:0;
28
+ margin-bottom:16px;
29
+ }
30
+ h2 span {
31
+ color:#666666;
32
+ }
33
+ header table {
34
+ border-collapse:collapse;
35
+ }
36
+ header table th {
37
+ text-align:right;
38
+ font-weight:bold;
39
+ padding-right:12px;
40
+ }
41
+ header table th, header table td {
42
+ padding:4px 6px;
43
+ }
44
+ nav {
45
+ display:block;
46
+ padding:12px 12px;
47
+ border-bottom:1px solid #000000;
48
+ background-color: #444444;
49
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#444444), to(#222222)); /* Safari 4+, Chrome */
50
+ background-image: -webkit-linear-gradient(top, #444444, #222222); /* Chrome 10+, Safari 5.1+, iOS 5+ */
51
+ background-image: -moz-linear-gradient(top, #444444, #222222); /* Firefox 3.6-15 */
52
+ background-image: -o-linear-gradient(top, #444444, #222222); /* Opera 11.10-12.00 */
53
+ background-image: linear-gradient(to bottom, #444444, #222222); /* Firefox 16+, IE10, Opera 12.50+ */
54
+ }
55
+ nav a {
56
+ color:#ffffff;
57
+ text-decoration:none;
58
+ padding:4px 16px;
59
+ border-radius:16px;
60
+ }
61
+ nav a:hover {
62
+ background-color: #666666;
63
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#666666), to(#444444)); /* Safari 4+, Chrome */
64
+ background-image: -webkit-linear-gradient(top, #666666, #444444); /* Chrome 10+, Safari 5.1+, iOS 5+ */
65
+ background-image: -moz-linear-gradient(top, #666666, #444444); /* Firefox 3.6-15 */
66
+ background-image: -o-linear-gradient(top, #666666, #444444); /* Opera 11.10-12.00 */
67
+ background-image: linear-gradient(to bottom, #666666, #444444); /* Firefox 16+, IE10, Opera 12.50+ */
68
+ }
69
+ nav a.selected {
70
+ color:#000000;
71
+ background-color: #efefef;
72
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#cfcfcf)); /* Safari 4+, Chrome */
73
+ background-image: -webkit-linear-gradient(top, #efefef, #cfcfcf); /* Chrome 10+, Safari 5.1+, iOS 5+ */
74
+ background-image: -moz-linear-gradient(top, #efefef, #cfcfcf); /* Firefox 3.6-15 */
75
+ background-image: -o-linear-gradient(top, #efefef, #cfcfcf); /* Opera 11.10-12.00 */
76
+ background-image: linear-gradient(to bottom, #efefef, #cfcfcf); /* Firefox 16+, IE10, Opera 12.50+ */
77
+ }
78
+ .frames {
79
+ width:50%;
80
+ float:left;
81
+ }
82
+ .frames li {
83
+ list-style:none;
84
+ display:block;
85
+ padding:12px;
86
+ overflow:hidden;
87
+ cursor:pointer;
88
+ background-color: #f4f7ff;
89
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f7ff), to(#e4ebfe)); /* Safari 4+, Chrome */
90
+ background-image: -webkit-linear-gradient(top, #f4f7ff, #e4ebfe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
91
+ background-image: -moz-linear-gradient(top, #f4f7ff, #e4ebfe); /* Firefox 3.6-15 */
92
+ background-image: -o-linear-gradient(top, #f4f7ff, #e4ebfe); /* Opera 11.10-12.00 */
93
+ background-image: linear-gradient(to bottom, #f4f7ff, #e4ebfe); /* Firefox 16+, IE10, Opera 12.50+ */
94
+ border-bottom:1px solid #cccccc;
95
+ }
96
+ .frames li:hover, .frames li.selected {
97
+ background-color: #e4ebfe;
98
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#e4ebfe), to(#c2d3fe)); /* Safari 4+, Chrome */
99
+ background-image: -webkit-linear-gradient(top, #e4ebfe, #c2d3fe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
100
+ background-image: -moz-linear-gradient(top, #e4ebfe, #c2d3fe); /* Firefox 3.6-15 */
101
+ background-image: -o-linear-gradient(top, #e4ebfe, #c2d3fe); /* Opera 11.10-12.00 */
102
+ background-image: linear-gradient(to bottom, #e4ebfe, #c2d3fe); /* Firefox 16+, IE10, Opera 12.50+ */
103
+ }
104
+ .frames .name, .frame_info h2.name {
105
+ font-family:Monaco, Incosolata, Consolas, monospace;
106
+ font-size:14px;
107
+ margin-bottom:4px;
108
+ word-wrap:break-word;
109
+ }
110
+ .location {
111
+ color:#888888;
112
+ }
113
+ .location .filename, .location .line {
114
+ color:#333333;
115
+ }
116
+ .frame_info {
117
+ background-color:#efefef;
118
+ border-bottom:1px solid #cccccc;
119
+ float:left;
120
+ width:50%;
121
+ }
122
+ .frame_info > * {
123
+ margin:16px;
124
+ }
125
+ .frame_info h2 {
126
+ font-family:Monaco, Incosolata, Consolas, monospace;
127
+ }
128
+ .frame_info .code {
129
+ font-family:Monaco, Incosolata, Consolas, monospace;
130
+ background-color:#ffffff;
131
+ border:1px solid #cccccc;
132
+ border-collapse:collapse;
133
+ overflow:auto;
134
+ }
135
+ .frame_info .code pre:nth-child(2n+1) {
136
+ background-color:#f9f9f9;
137
+ }
138
+ .frame_info .code pre:nth-child(2n) {
139
+ background-color:#f3f3f3;
140
+ }
141
+ .frame_info .code pre.highlight {
142
+ background-color:#ffeaaa;
143
+ }
144
+ .frame_info .code .line {
145
+ text-align:right;
146
+ padding-right:12px;
147
+ padding-left:0px;
148
+ }
149
+ code {
150
+ background-color:#f0f0f0;
151
+ padding:2px 4px;
152
+ border:1px solid #d0d0d0;
153
+ }
154
+ h3 {
155
+ font-weight:normal;
156
+ border-top:1px solid #cccccc;
157
+ padding-top:16px;
158
+ margin-bottom:16px;
159
+ }
160
+ .var_table {
161
+ border-collapse:collapse;
162
+ }
163
+ .var_table td {
164
+ padding:8px;
165
+ }
166
+ .var_table td.name {
167
+ font-style:italic;
168
+ }
169
+ .var_table tr {
170
+ border-bottom:1px solid #cccccc;
171
+ }
172
+ .repl .console {
173
+ background-color:#ffffff;
174
+ padding:2px 4px;
175
+ border:1px solid #d0d0d0;
176
+ margin-bottom:8px;
177
+ font-family:monospace;
178
+ }
179
+ .repl pre {
180
+ font-size:12px;
181
+ white-space:pre-wrap;
182
+ max-height:400px;
183
+ overflow:auto;
184
+ }
185
+ .repl .prompt {
186
+ font-size:12px;
187
+ position:relative;
188
+ }
189
+ .repl input {
190
+ font-size:12px;
191
+ border:none;
192
+ font-family:monospace;
193
+ outline:none;
194
+ padding:0px;
195
+ position:absolute;
196
+ left:20px;
197
+ right:0px;
198
+ }
199
+ </style>
200
+ </head>
201
+ <body>
202
+ <header>
203
+ <h2><%= exception.class %> <span>at <%= request_path %></span></h2>
204
+ <table>
205
+ <tr>
206
+ <th>Message</th>
207
+ <td><%= exception.message %></td>
208
+ </tr>
209
+ <% if backtrace_frames.any? %>
210
+ <tr>
211
+ <th>File</th>
212
+ <td><%= backtrace_frames.first.filename %></td>
213
+ </tr>
214
+ <tr>
215
+ <th>Line</th>
216
+ <td><%= backtrace_frames.first.line %></td>
217
+ </tr>
218
+ <% end %>
219
+ </table>
220
+ </header>
221
+ <nav>
222
+ <a href="#" id="application_frames">Application Frames</a>
223
+ <a href="#" id="all_frames">All Frames</a>
224
+ </nav>
225
+ <section class="backtrace">
226
+ <ul class="frames">
227
+ <% backtrace_frames.each_with_index do |frame, index| %>
228
+ <li
229
+ data-context="<%= frame.context %>"
230
+ data-full-filename="<%= frame.filename %>"
231
+ data-filename="<%= frame.pretty_path %>"
232
+ data-line="<%= frame.line %>"
233
+ data-name="<%= frame.name %>"
234
+ data-index="<%= index %>"
235
+ >
236
+ <div class="name"><%= frame.name %></div>
237
+ <div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
238
+ </li>
239
+ <% end %>
240
+ </ul>
241
+ <% backtrace_frames.each_with_index do |frame, index| %>
242
+ <div class="frame_info" id="frame_info_<%= index %>" style="display:none;">
243
+ <h2 class="name"><%= frame.name %></h2>
244
+ <div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
245
+ <%== highlighted_code_block frame %>
246
+
247
+ <% if BetterErrors.binding_of_caller_available? %>
248
+ <div class="repl">
249
+ <h3>REPL</h3>
250
+ <div class="console">
251
+ <pre></pre>
252
+ <div class="prompt"><span>&gt;&gt;</span> <input/></div>
253
+ </div>
254
+ </div>
255
+
256
+ <div class="variable_info"></div>
257
+ <% else %>
258
+ <h3>Advanced features unavailable</h3>
259
+ <p class="error">
260
+ You must add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
261
+ </p>
262
+ <% end %>
263
+ </div>
264
+ <% end %>
265
+ <div style="clear:both"></div>
266
+ </section>
267
+ </body>
268
+ <script>
269
+ (function() {
270
+ var OID = <%== object_id.to_s.inspect %>;
271
+
272
+ var previousFrame = null;
273
+ var previousFrameInfo = null;
274
+ var allFrames = document.querySelectorAll("ul.frames li");
275
+ var allFrameInfos = document.querySelectorAll(".frame_info");
276
+
277
+ function apiCall(method, opts, cb) {
278
+ var req = new XMLHttpRequest();
279
+ req.open("POST", "/__better_errors/" + OID + "/" + method, true);
280
+ req.setRequestHeader("Content-Type", "application/json");
281
+ req.send(JSON.stringify(opts));
282
+ req.onreadystatechange = function() {
283
+ if(req.readyState == 4) {
284
+ var res = JSON.parse(req.responseText);
285
+ cb(res);
286
+ }
287
+ };
288
+ }
289
+
290
+ function escapeHTML(html) {
291
+ return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
292
+ }
293
+
294
+ function REPL(index) {
295
+ this.index = index;
296
+
297
+ this.previousCommands = [];
298
+ this.previousCommandOffset = 0;
299
+ }
300
+
301
+ REPL.all = [];
302
+
303
+ REPL.prototype.install = function(containerElement) {
304
+ this.container = containerElement;
305
+
306
+ this.promptElement = this.container.querySelector(".prompt span");
307
+ this.inputElement = this.container.querySelector("input");
308
+ this.outputElement = this.container.querySelector("pre");
309
+
310
+ this.inputElement.onkeydown = this.onKeyDown.bind(this);
311
+
312
+ this.setPrompt(">>");
313
+
314
+ REPL.all[this.index] = this;
315
+ }
316
+
317
+ REPL.prototype.focus = function() {
318
+ this.inputElement.focus();
319
+ };
320
+
321
+ REPL.prototype.setPrompt = function(prompt) {
322
+ this._prompt = prompt;
323
+ this.promptElement.innerHTML = escapeHTML(prompt);
324
+ };
325
+
326
+ REPL.prototype.getInput = function() {
327
+ return this.inputElement.value;
328
+ };
329
+
330
+ REPL.prototype.setInput = function(text) {
331
+ this.inputElement.value = text;
332
+
333
+ if(this.inputElement.setSelectionRange) {
334
+ // set cursor to end of input
335
+ this.inputElement.setSelectionRange(text.length, text.length);
336
+ }
337
+ };
338
+
339
+ REPL.prototype.writeRawOutput = function(output) {
340
+ this.outputElement.innerHTML += output;
341
+ this.outputElement.scrollTop = this.outputElement.scrollHeight;
342
+ };
343
+
344
+ REPL.prototype.writeOutput = function(output) {
345
+ this.writeRawOutput(escapeHTML(output));
346
+ };
347
+
348
+ REPL.prototype.sendInput = function(line) {
349
+ var self = this;
350
+ apiCall("eval", { "index": this.index, source: line }, function(response) {
351
+ if(response.error) {
352
+ self.writeOutput(response.error + "\n");
353
+ }
354
+ self.writeOutput(self._prompt + " ");
355
+ self.writeRawOutput(response.highlighted_input + "\n");
356
+ self.writeOutput(response.result);
357
+ self.setPrompt(response.prompt);
358
+ });
359
+ };
360
+
361
+ REPL.prototype.onEnterKey = function() {
362
+ var text = this.getInput();
363
+ if(text != "" && text !== undefined) {
364
+ this.previousCommandOffset = this.previousCommands.push(text);
365
+ }
366
+ this.setInput("");
367
+ this.sendInput(text);
368
+ };
369
+
370
+ REPL.prototype.onNavigateHistory = function(direction) {
371
+ this.previousCommandOffset += direction;
372
+
373
+ if(this.previousCommandOffset < 0) {
374
+ this.previousCommandOffset = -1;
375
+ this.setInput("");
376
+ return;
377
+ }
378
+
379
+ if(this.previousCommandOffset >= this.previousCommands.length) {
380
+ this.previousCommandOffset = this.previousCommands.length;
381
+ this.setInput("");
382
+ return;
383
+ }
384
+
385
+ this.setInput(this.previousCommands[this.previousCommandOffset]);
386
+ };
387
+
388
+ REPL.prototype.onKeyDown = function(ev) {
389
+ if(ev.keyCode == 13) {
390
+ this.onEnterKey();
391
+ } else if(ev.keyCode == 38) {
392
+ // the user pressed the up arrow.
393
+ this.onNavigateHistory(-1);
394
+ return false;
395
+ } else if(ev.keyCode == 40) {
396
+ // the user pressed the down arrow.
397
+ this.onNavigateHistory(1);
398
+ return false;
399
+ }
400
+ };
401
+
402
+ function populateVariableInfo(index) {
403
+ var el = allFrameInfos[index];
404
+ var varInfo = el.querySelector(".variable_info");
405
+ if(varInfo && varInfo.innerHTML == "") {
406
+ apiCall("variables", { "index": index }, function(response) {
407
+ if(response.error) {
408
+ varInfo.innerHTML = "<span class='error'>" + escapeHTML(response.error) + "</span>";
409
+ } else {
410
+ varInfo.innerHTML = response.html;
411
+ }
412
+ });
413
+ }
414
+ }
415
+
416
+ function selectFrameInfo(index) {
417
+ populateVariableInfo(index);
418
+
419
+ if(previousFrameInfo) {
420
+ previousFrameInfo.style.display = "none";
421
+ }
422
+ previousFrameInfo = allFrameInfos[index];
423
+ previousFrameInfo.style.display = "block";
424
+
425
+ if(REPL.all[index]) {
426
+ REPL.all[index].focus();
427
+ }
428
+ }
429
+
430
+ for(var i = 0; i < allFrames.length; i++) {
431
+ (function(i, el) {
432
+ var el = allFrames[i];
433
+ el.onclick = function() {
434
+ if(previousFrame) {
435
+ previousFrame.className = "";
436
+ }
437
+ el.className = "selected";
438
+ previousFrame = el;
439
+
440
+ selectFrameInfo(el.attributes["data-index"].value);
441
+ };
442
+ var repl = allFrameInfos[i].querySelector(".repl .console");
443
+ if(repl) {
444
+ new REPL(i).install(repl);
445
+ }
446
+ })(i);
447
+ }
448
+
449
+ allFrames[0].click();
450
+
451
+ var applicationFramesButton = document.getElementById("application_frames");
452
+ var allFramesButton = document.getElementById("all_frames");
453
+
454
+ applicationFramesButton.onclick = function() {
455
+ allFramesButton.className = "";
456
+ applicationFramesButton.className = "selected";
457
+ for(var i = 0; i < allFrames.length; i++) {
458
+ if(allFrames[i].attributes["data-context"].value == "application") {
459
+ allFrames[i].style.display = "block";
460
+ } else {
461
+ allFrames[i].style.display = "none";
462
+ }
463
+ }
464
+ return false;
465
+ };
466
+
467
+ allFramesButton.onclick = function() {
468
+ applicationFramesButton.className = "";
469
+ allFramesButton.className = "selected";
470
+ for(var i = 0; i < allFrames.length; i++) {
471
+ allFrames[i].style.display = "block";
472
+ }
473
+ return false;
474
+ };
475
+
476
+ applicationFramesButton.click();
477
+ })();
478
+ </script>
479
+ </html>
480
+ <!-- generated by Better Errors in <%= Time.now.to_f - @start_time %> seconds -->
@@ -0,0 +1,13 @@
1
+ <h3>Local Variables</h3>
2
+ <table class="var_table">
3
+ <% @frame.local_variables.each do |name, value| %>
4
+ <tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
5
+ <% end %>
6
+ </table>
7
+
8
+ <h3>Instance Variables</h3>
9
+ <table class="var_table">
10
+ <% @frame.instance_variables.each do |name, value| %>
11
+ <tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
12
+ <% end %>
13
+ </table>
@@ -1,3 +1,3 @@
1
1
  module BetterErrors
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/better_errors.rb CHANGED
@@ -4,11 +4,13 @@ require "coderay"
4
4
 
5
5
  require "better_errors/version"
6
6
  require "better_errors/error_page"
7
- require "better_errors/error_frame"
7
+ require "better_errors/stack_frame"
8
8
  require "better_errors/middleware"
9
+ require "better_errors/code_formatter"
10
+ require "better_errors/repl"
9
11
 
10
12
  class << BetterErrors
11
- attr_accessor :application_root, :binding_of_caller_available
13
+ attr_accessor :application_root, :binding_of_caller_available, :logger
12
14
 
13
15
  alias_method :binding_of_caller_available?, :binding_of_caller_available
14
16
  end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ module BetterErrors
4
+ describe CodeFormatter do
5
+ let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
6
+
7
+ let(:formatter) { CodeFormatter.new(filename, 8) }
8
+
9
+ it "should pick an appropriate scanner" do
10
+ formatter.coderay_scanner.should == :ruby
11
+ end
12
+
13
+ it "should show 5 lines of context" do
14
+ formatter.line_range.should == (3..13)
15
+
16
+ formatter.context_lines.should == [
17
+ "three\n",
18
+ "four\n",
19
+ "five\n",
20
+ "six\n",
21
+ "seven\n",
22
+ "eight\n",
23
+ "nine\n",
24
+ "ten\n",
25
+ "eleven\n",
26
+ "twelve\n",
27
+ "thirteen\n"
28
+ ]
29
+ end
30
+
31
+ it "should highlight the erroring line" do
32
+ formatter.html.should =~ /highlight.*eight/
33
+ end
34
+
35
+ it "should work when the line is right on the edge" do
36
+ formatter = CodeFormatter.new(filename, 20)
37
+ formatter.line_range.should == (15..20)
38
+ formatter.html.should_not == formatter.source_unavailable
39
+ end
40
+
41
+ it "should not barf when the lines don't make any sense" do
42
+ formatter = CodeFormatter.new(filename, 999)
43
+ formatter.html.should == formatter.source_unavailable
44
+ end
45
+
46
+ it "should not barf when the file doesn't exist" do
47
+ formatter = CodeFormatter.new("fkdguhskd7e l", 1)
48
+ formatter.html.should == formatter.source_unavailable
49
+ end
50
+ end
51
+ end
@@ -7,6 +7,16 @@ module BetterErrors
7
7
  let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } }
8
8
 
9
9
  let(:response) { error_page.render }
10
+
11
+ let(:empty_binding) {
12
+ local_a = :value_for_local_a
13
+ local_b = :value_for_local_b
14
+
15
+ @inst_c = :value_for_inst_c
16
+ @inst_d = :value_for_inst_d
17
+
18
+ binding
19
+ }
10
20
 
11
21
  it "should include the error message" do
12
22
  response.should include("you divided by zero you silly goose!")
@@ -20,26 +30,31 @@ module BetterErrors
20
30
  response.should include("ZeroDivisionError")
21
31
  end
22
32
 
23
- context "when showing source code" do
24
- before do
25
- exception.stub!(:backtrace).and_return([
26
- "#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
27
- ])
28
- end
33
+ context "variable inspection" do
34
+ let(:exception) { empty_binding.eval("raise") rescue $! }
29
35
 
30
- it "should show the line where the exception was raised" do
31
- response.should include("8 eight")
36
+ it "should show local variables" do
37
+ html = error_page.do_variables("index" => 0)[:html]
38
+ html.should include("local_a")
39
+ html.should include(":value_for_local_a")
40
+ html.should include("local_b")
41
+ html.should include(":value_for_local_b")
32
42
  end
33
43
 
34
- it "should show five lines of context" do
35
- response.should include("3 three")
36
- response.should include("13 thirteen")
37
- end
38
-
39
- it "should not show more than five lines of context" do
40
- response.should_not include("2 two")
41
- response.should_not include("14 fourteen")
44
+ it "should show instance variables" do
45
+ html = error_page.do_variables("index" => 0)[:html]
46
+ html.should include("inst_c")
47
+ html.should include(":value_for_inst_c")
48
+ html.should include("inst_d")
49
+ html.should include(":value_for_inst_d")
42
50
  end
43
51
  end
52
+
53
+ it "should not die if the source file is not a real filename" do
54
+ exception.stub!(:backtrace).and_return([
55
+ "<internal:prelude>:10:in `spawn_rack_application'"
56
+ ])
57
+ response.should include("Source unavailable")
58
+ end
44
59
  end
45
60
  end
@@ -21,6 +21,14 @@ module BetterErrors
21
21
 
22
22
  headers["Content-Type"].should == "text/html; charset=utf-8"
23
23
  end
24
+
25
+ it "should log the exception" do
26
+ logger = Object.new
27
+ logger.should_receive :fatal
28
+ BetterErrors.stub!(:logger).and_return(logger)
29
+
30
+ app.call({})
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+ require "better_errors/repl/basic"
3
+
4
+ module BetterErrors
5
+ module REPL
6
+ describe Basic do
7
+ let(:fresh_binding) {
8
+ local_a = 123
9
+ binding
10
+ }
11
+
12
+ let(:repl) { Basic.new fresh_binding }
13
+
14
+ it "should evaluate ruby code in a given context" do
15
+ repl.send_input("local_a = 456")
16
+ fresh_binding.eval("local_a").should == 456
17
+ end
18
+
19
+ it "should return a tuple of output and the new prompt" do
20
+ output, prompt = repl.send_input("1 + 2")
21
+ output.should == "=> 3\n"
22
+ prompt.should == ">>"
23
+ end
24
+
25
+ it "should not barf if the code throws an exception" do
26
+ output, prompt = repl.send_input("raise Exception")
27
+ output.should == "!! #<Exception: Exception>\n"
28
+ prompt.should == ">>"
29
+ end
30
+ end
31
+ end
32
+ end