debug-bar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ require 'pp'
2
+ require 'awesome_print'
3
+ require 'cgi'
4
+
5
+ module DebugBar
6
+ module RecipeBook
7
+ # A default RecipeBook with recipes useful for Rails applications.
8
+ class Default < Base
9
+
10
+ # Displays params in a user readable fashion.
11
+ #
12
+ # If the :cutoff option is given, it auto-hides when the params are
13
+ # more characters in length than the cutoff, otherwise it defaults to
14
+ # a sane length.
15
+ def params_recipe(opts={})
16
+ return Proc.new do |b|
17
+ params_s = b[:params].awesome_print_html
18
+ ['Params', params_s, {:id => 'params'}]
19
+ end
20
+ end
21
+
22
+ # Displays the session in a pretty printed way.
23
+ def session_recipe
24
+ return Proc.new {|b| ['Session', b[:session].awesome_print_html, {:id => 'session'}]}
25
+ end
26
+
27
+ # Displays the cookies.
28
+ def cookies_recipe
29
+ return Proc.new {|b| ['Cookies', b[:cookies].awesome_print_html, {:id => 'cookies'}]}
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'recipe_book/base'
2
+ require_relative 'recipe_book/default'
@@ -0,0 +1,3 @@
1
+ module DebugBar
2
+ VERSION = '1.0.0'
3
+ end
data/lib/debug-bar.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'debug-bar/ext'
2
+
3
+ require_relative 'debug-bar/base'
4
+ require_relative 'debug-bar/default'
5
+
6
+ require_relative 'debug-bar/recipe_book'
@@ -0,0 +1,12 @@
1
+ <div class="callback-box dbar-togglable" style="border:1px solid gray; margin:4px; padding:4px;">
2
+ <% if opts[:id] %>
3
+ <a href="" id="<%= opts[:id] %>" class='dbar-toggle persistent'>
4
+ <% else %>
5
+ <a href="" class='dbar-toggle'>
6
+ <% end %>
7
+ [x] <span class="title" style="font-weight:bold; color: #000000;"><%= title %></span>
8
+ </a>
9
+ <div class="dbar-content <%= 'show' unless opts[:hidden] %>" <%= 'style="display:none"' if opts[:hidden] %> >
10
+ <%= content %>
11
+ </div>
12
+ </div>
@@ -0,0 +1,6 @@
1
+ <div class="error">
2
+ <pre>
3
+ <%= error.class.to_s.html_escape %>:<%= error.message.to_s.html_escape %>
4
+ </pre>
5
+ <%= error.backtrace.awesome_print_html %>
6
+ </div>
@@ -0,0 +1,142 @@
1
+ <style>
2
+ .dbar-content pre {
3
+ background: #a9a9a9;
4
+ margin: 0;
5
+ padding: 0;
6
+ font-size: 10px;
7
+ font-family: "Lucida Console", Monaco, monospace;
8
+ }
9
+
10
+ .dbar-content pre pre {
11
+ background: #a9a9a9;
12
+ margin: 0;
13
+ padding: 0;
14
+ border: 0;
15
+ font-size: 10px;
16
+ font-family: "Lucida Console", Monaco, monospace;
17
+ display: inline;
18
+ }
19
+
20
+ </style>
21
+
22
+ <div id="debug-bar" style="text-align:left; border:1px solid yellow; background:#ffffaa; width:24px; position:fixed; top:0; left:0; z-index:999">
23
+ <a href="" id="debug-toggle">[x]</a>
24
+ <div id="debug-data" style="font-size:9pt; display:none; overflow:auto;">
25
+ <%= content %>
26
+ </div>
27
+ </div>
28
+
29
+ <script>
30
+ /*!
31
+ * jQuery Cookie Plugin
32
+ * https://github.com/carhartl/jquery-cookie
33
+ *
34
+ * Copyright 2011, Klaus Hartl
35
+ * Dual licensed under the MIT or GPL Version 2 licenses.
36
+ * http://www.opensource.org/licenses/mit-license.php
37
+ * http://www.opensource.org/licenses/GPL-2.0
38
+ */
39
+ (function($) {
40
+ $.cookie = function(key, value, options) {
41
+
42
+ // key and at least value given, set cookie...
43
+ if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
44
+ options = $.extend({}, options);
45
+
46
+ if (value === null || value === undefined) {
47
+ options.expires = -1;
48
+ }
49
+
50
+ if (typeof options.expires === 'number') {
51
+ var days = options.expires, t = options.expires = new Date();
52
+ t.setDate(t.getDate() + days);
53
+ }
54
+
55
+ value = String(value);
56
+
57
+ return (document.cookie = [
58
+ encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
59
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
60
+ options.path ? '; path=' + options.path : '',
61
+ options.domain ? '; domain=' + options.domain : '',
62
+ options.secure ? '; secure' : ''
63
+ ].join(''));
64
+ }
65
+
66
+ // key and possibly options given, get cookie...
67
+ options = value || {};
68
+ var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
69
+
70
+ var pairs = document.cookie.split('; ');
71
+ for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
72
+ if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
73
+ }
74
+ return null;
75
+ };
76
+ })(jQuery);
77
+ </script>
78
+ <script>
79
+ jQuery('document').ready(function(){
80
+ var toggleDebugBar = function(){
81
+ var data = jQuery('#debug-data').toggle();
82
+ jQuery('#debug-bar').css('width', data.is(':visible') ? 'auto' : '24px');
83
+ jQuery('#debug-bar').css('max-height', data.is(':visible') ? '100%' : '24px');
84
+ jQuery('#debug-bar').css('overflow-y', data.is(':visible') ? 'auto' : 'hidden');
85
+ };
86
+
87
+ jQuery('#debug-toggle').on('click', function(e){
88
+ toggleDebugBar();
89
+ return false;
90
+ });
91
+
92
+ jQuery('body').bind('keydown', function(e){
93
+ if(e.keyCode == 192 && e.ctrlKey == true)
94
+ toggleDebugBar();
95
+ });
96
+
97
+ // Debug-bar section specific toggle (may differ from generic toggle.
98
+ // This is meant to be consumable by callback makers.
99
+ jQuery('.dbar-toggle').bind('click', function(){
100
+ var toggle = jQuery(this);
101
+ var content = toggle.closest('.dbar-togglable').find('.dbar-content');
102
+ content.first().toggle();
103
+ return false;
104
+ });
105
+
106
+ // Generic togglable inside of debug bar callback content area.
107
+ jQuery('.dbar-content').find('.toggle-switch').click(function(){
108
+ var toggle_content = jQuery(this).siblings('.toggle-content');
109
+ toggle_content.toggle();
110
+ return false;
111
+ });
112
+
113
+ jQuery('.callback-box .dbar-toggle.persistent').bind('click', function() {
114
+ // caching
115
+ var toggle = jQuery(this);
116
+ var id = toggle.attr('id');
117
+
118
+ // Deal with an empty cookie and split the commas
119
+ var debug_bar_array = jQuery.cookie('debug_bar') ? jQuery.cookie('debug_bar').split(',') : [];
120
+
121
+ // Just a double check it has an id
122
+ if (id)
123
+ {
124
+ // Decide which way to toggle this key
125
+ var id_index = debug_bar_array.indexOf(id);
126
+ if (id_index > -1)
127
+ {
128
+ // Remove id from cookie
129
+ debug_bar_array.splice(id_index, 1);
130
+ }
131
+ else
132
+ {
133
+ // Add the id into the list to keep open
134
+ debug_bar_array.push(id);
135
+ }
136
+
137
+ // Rejoin the cookie keys and store
138
+ jQuery.cookie('debug_bar', debug_bar_array.join(','), { path: '/', expires: 7 });
139
+ }
140
+ });
141
+ });
142
+ </script>
data/spec/base_spec.rb ADDED
@@ -0,0 +1,363 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class TestDefaultRecipeBook < DebugBar::RecipeBook::Base
5
+
6
+ def params_recipe(opts={})
7
+ return Proc.new do |b|
8
+ params_s = b[:params].awesome_print_html
9
+ hidden = params_s.length > opts.fetch(:cutoff, 512)
10
+ puts [params_s.length, hidden, opts].inspect
11
+ ['Params', params_s, {:hidden => hidden, :id => 'params'}]
12
+ end
13
+ end
14
+
15
+ def block_test_recipe(arg=nil)
16
+ return Proc.new do |b|
17
+ ['Block Test', "|arg|#{arg}|arg| |block|#{yield(b) if block_given?}|block|", {}]
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ describe DebugBar::Base do
24
+
25
+ describe 'callbacks' do
26
+
27
+ before(:each) do
28
+ @debug_bar = DebugBar::Base.new
29
+ end
30
+
31
+ it 'should register recipes in initializer after the block is called' do
32
+ debug_bar = DebugBar::Base.new(:params) do |bar|
33
+ bar.add_recipe_book(TestDefaultRecipeBook)
34
+ bar.callbacks.length.should == 0
35
+ end
36
+
37
+ debug_bar.callbacks.length.should == 1
38
+ end
39
+
40
+
41
+ it 'should register blocks' do
42
+ @debug_bar.add {|b| ["Test Block", "Test Content", {}]}
43
+
44
+ @debug_bar.callbacks.length.should == 1
45
+ @debug_bar.callbacks.first.should be_kind_of(Proc)
46
+ end
47
+
48
+ it 'should register recipes' do
49
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
50
+ @debug_bar.add(:params)
51
+
52
+ @debug_bar.callbacks.length.should == 1
53
+ @debug_bar.callbacks.first.should be_kind_of(Proc)
54
+ end
55
+
56
+ it 'should error on nil' do
57
+ lambda {@debug_bar.add(nil)}.should raise_error(ArgumentError)
58
+ end
59
+
60
+ end
61
+
62
+ describe 'render' do
63
+
64
+ before(:each) do
65
+ @debug_bar = DebugBar::Base.new
66
+ end
67
+
68
+ it 'should render blocks' do
69
+ title = "Testing The Block"
70
+ content = "<pre>Alpha Beta</pre>"
71
+ @debug_bar.add {|b| [title, content, {}]}
72
+
73
+ html = @debug_bar.render(binding)
74
+
75
+ html.should be_kind_of(String)
76
+ html.should_not be_empty
77
+
78
+ html.index(title).should_not be_nil
79
+ html.index(content).should_not be_nil
80
+ end
81
+
82
+ it 'should render as html_safe string' do
83
+ @debug_bar.add {|b| ["foo", "bar", {}]}
84
+ html = @debug_bar.render(binding)
85
+ html.should be_kind_of(ActiveSupport::SafeBuffer)
86
+ end
87
+
88
+ it 'should render with a given binding' do
89
+ @debug_bar.add {|b| ["Title", "||#{b.eval('foobar')}||", {}]}
90
+
91
+ foobar = "Spoilers!"
92
+ html = @debug_bar.render(binding)
93
+
94
+ html.should be_kind_of(String)
95
+
96
+ html.index(foobar).should_not be_nil
97
+ end
98
+
99
+ it 'should render with a custom decorated binding' do
100
+ @debug_bar.add {|b| ["Binding", "||get is #{b.respond_to?(:[])}||", {}]}
101
+
102
+ binding.should_not respond_to(:[])
103
+ html = @debug_bar.render(binding)
104
+
105
+ html.index("||get is true||").should_not be_nil
106
+ end
107
+
108
+ it 'should render recipes' do
109
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
110
+ @debug_bar.add(:params)
111
+ params = {:given_name => 'Amelia', :family_name => 'Pond'}
112
+
113
+ html = @debug_bar.render(binding)
114
+
115
+ html.should be_kind_of(String)
116
+ html.should_not be_empty
117
+
118
+ html.index('Amelia').should_not be_nil
119
+ html.index(':given_name').should_not be_nil
120
+ html.index('dbar-content show').should_not be_nil # See if params was expanded.
121
+ end
122
+
123
+ it 'should render recipes with args' do
124
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
125
+ @debug_bar.add(:params, :cutoff => 12)
126
+
127
+ params = {:given_name => 'Amelia', :family_name => 'Pond'}
128
+
129
+ html = @debug_bar.render(binding)
130
+
131
+ html.should be_kind_of(String)
132
+ html.should_not be_empty
133
+
134
+ html.index('Amelia').should_not be_nil
135
+ html.index(':given_name').should_not be_nil
136
+ html.index('dbar-content show').should be_nil # See if params was collapsed due to cutoff.
137
+ end
138
+
139
+ it 'should render recipes with block args' do
140
+ @debug_bar.add_recipe_book(TestDefaultRecipeBook)
141
+ @debug_bar.add(:block_test) {|b| b.class.name}
142
+
143
+ html = @debug_bar.render(binding)
144
+
145
+ html.should be_kind_of(String)
146
+ html.should_not be_empty
147
+
148
+ html.index('|block|Binding|block|').should_not be_nil
149
+ end
150
+
151
+ it 'should render the callback_box' do
152
+ @debug_bar.add {|b| ["foo", "bar", {}]}
153
+ html = @debug_bar.render(binding)
154
+
155
+ html.index('callback-box').should_not be_nil # Picked the CSS class as a good indicator of presence.
156
+ end
157
+
158
+ it 'should render the layout' do
159
+ @debug_bar.add {|b| ["foo", "bar", {}]}
160
+ html = @debug_bar.render(binding)
161
+
162
+ html.index('debug-bar').should_not be_nil # Picked the presence of CSS class as a good inidcator of presence.
163
+ end
164
+
165
+ it 'should render on crash in callback' do
166
+ @debug_bar.add {|b| raise RuntimeError, "Uh-oh, you didn't handle the exception!"}
167
+ html = ''
168
+ lambda {html = @debug_bar.render(binding)}.should_not raise_error
169
+ html.index('ERROR').should_not be_nil
170
+ end
171
+
172
+ end
173
+
174
+ describe 'options' do
175
+
176
+ before(:each) do
177
+ @debug_bar = DebugBar::Base.new
178
+ end
179
+
180
+ it 'should interpret missing opts from callback as {}' do
181
+ @debug_bar.add {|b| ['foo', 'bar']}
182
+
183
+ html = @debug_bar.render(binding)
184
+
185
+ html.should be_kind_of(String)
186
+ html.should_not be_empty
187
+
188
+ html.index('foo').should_not be_nil
189
+ html.index('bar').should_not be_nil
190
+ end
191
+
192
+ it 'should interpret nil opts from callback as {}' do
193
+ @debug_bar.add {|b| ['foo', 'bar', nil]}
194
+
195
+ html = @debug_bar.render(binding)
196
+
197
+ html.should be_kind_of(String)
198
+ html.should_not be_empty
199
+
200
+ html.index('foo').should_not be_nil
201
+ html.index('bar').should_not be_nil
202
+ end
203
+
204
+ it 'should default :hidden option to true if not explicitly given and session remembers it as open based on id' do
205
+ # Emulate saving of open callback box ids in cookies.
206
+ cookies = {:debug_bar => 'rory_williams,amelia_pond'}
207
+ # Put into binding.
208
+ b = binding
209
+
210
+ html_rory = DebugBar::Base.new.add {|b| ['foo', 'bar', :id => 'rory_williams']}.render(b)
211
+ html_rose = DebugBar::Base.new.add {|b| ['foo', 'bar', :id => 'rose_tyler']}.render(b)
212
+
213
+ # Extract the classes from the first div that has a dbar-content class in it.
214
+ extract_classes = lambda do |html|
215
+ m = /<div[^>]+class=['"]([^>'"]*dbar-content[^>'"]*)[^>]+>/.match(html)
216
+ return m.to_a[1].to_s.split(/\s+/)
217
+ end
218
+
219
+ html_rory.should include('show')
220
+ html_rose.should_not include('show')
221
+ end
222
+
223
+ it 'should recognize :hidden options' do
224
+ html_hidden = DebugBar::Base.new.add {|b| ['foo', 'bar', :hidden => true]}.render(binding)
225
+ html_nohide = DebugBar::Base.new.add {|b| ['foo', 'bar', :hidden => false]}.render(binding)
226
+
227
+ # Extract the classes from the first div that has a dbar-content class in it.
228
+ extract_classes = lambda do |html|
229
+ m = /<div[^>]+class=['"]([^>'"]*dbar-content[^>'"]*)[^>]+>/.match(html)
230
+ return m.to_a[1].to_s.split(/\s+/)
231
+ end
232
+
233
+ extract_classes.call(html_hidden).should_not include('show')
234
+ extract_classes.call(html_nohide).should include('show')
235
+ end
236
+
237
+ end
238
+
239
+ describe 'recipe books' do
240
+
241
+ class TestBook < DebugBar::RecipeBook::Base
242
+ def duplicated_recipe
243
+ Proc.new {|b| :test_book_duplicated_recipe}
244
+ end
245
+
246
+ def beta_recipe
247
+ Proc.new {|b| :test_book_beta_recipe}
248
+ end
249
+
250
+ def some_helper
251
+ end
252
+ end
253
+
254
+ class AnotherTestBook < DebugBar::RecipeBook::Base
255
+ def duplicated_recipe
256
+ Proc.new {|b| :another_test_book_duplicated_recipe}
257
+ end
258
+
259
+ def gamma_recipe
260
+ Proc.new {|b| :another_test_book_gamma_recipe}
261
+ end
262
+ end
263
+
264
+ before(:each) do
265
+ @book_class = TestBook # TODO: This is a poor choice, we should create one just for testing.
266
+ @book = @book_class.new
267
+
268
+ @base_debug_bar = DebugBar::Base.new
269
+ @debug_bar = DebugBar::Base.new {|bar| bar.add_recipe_book(TestBook); bar.add_recipe_book(AnotherTestBook)}
270
+ end
271
+
272
+ it 'should add recipe books by class' do
273
+ @base_debug_bar.add_recipe_book(@book_class)
274
+
275
+ @base_debug_bar.recipe_books.length.should == 1
276
+ @base_debug_bar.recipe_books.first.should be_kind_of(@book_class)
277
+ end
278
+
279
+ it 'should add recipe books by instance' do
280
+ @base_debug_bar.add_recipe_book(@book)
281
+
282
+ @base_debug_bar.recipe_books.length.should == 1
283
+ @base_debug_bar.recipe_books.first.should be_kind_of(@book_class)
284
+ end
285
+
286
+ it 'should return the list of known recipes' do
287
+ @debug_bar.recipes.sort.should == [:beta, :duplicated, :gamma].sort
288
+ end
289
+
290
+ it 'should return a recipe callback if one is found' do
291
+ callback = @debug_bar.recipe_callback(:beta)
292
+ callback.should be_kind_of(Proc)
293
+ callback.call.should == :test_book_beta_recipe
294
+ end
295
+
296
+ it 'should raise an Argument Error if a recipe is not found' do
297
+ lambda {@debug_bar.recipe_callback(:zeta)}.should raise_error(ArgumentError)
298
+ end
299
+
300
+ it 'should use the last found instance of a recipe when it is duplicated' do
301
+ callback = @debug_bar.recipe_callback(:duplicated)
302
+ callback.should be_kind_of(Proc)
303
+ callback.call.should == :another_test_book_duplicated_recipe
304
+ end
305
+
306
+ end
307
+
308
+ describe 'subclass overrides' do
309
+
310
+ class SubclassTestBook < DebugBar::RecipeBook::Base
311
+
312
+ def beta_recipe
313
+ Proc.new {|b| :test_book_beta_recipe}
314
+ end
315
+
316
+ end
317
+
318
+ class SubclassInstanceTestBook < DebugBar::RecipeBook::Base
319
+
320
+ def foo_recipe
321
+ Proc.new {|b| :instance_test_book_foo_recipe}
322
+ end
323
+
324
+ end
325
+
326
+ class SubclassTestBar < DebugBar::Base
327
+
328
+ private
329
+
330
+ def default_recipe_books
331
+ return [SubclassTestBook, SubclassInstanceTestBook.new]
332
+ end
333
+
334
+ def default_recipes
335
+ return [:beta]
336
+ end
337
+
338
+ def template_search_paths
339
+ return :standin_for_an_array_of_pathnames
340
+ end
341
+
342
+ end
343
+
344
+ before(:each) do
345
+ @debug_bar = SubclassTestBar.new
346
+ end
347
+
348
+ it 'should add recipe books from the default recipe books method' do
349
+ @debug_bar.recipe_books.length.should == 2
350
+ @debug_bar.recipe_books.each {|book| book.should be_kind_of(DebugBar::RecipeBook::Base)}
351
+ end
352
+
353
+ it 'should add recipes from the default recipe method' do
354
+ @debug_bar.recipes.sort.should == [:beta, :foo].sort
355
+ end
356
+
357
+ it 'should allow overriding of the template path' do
358
+ @debug_bar.send(:template_search_paths).should == :standin_for_an_array_of_pathnames
359
+ end
360
+
361
+ end
362
+
363
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class TestContext
4
+ TARDIS = :tardis
5
+ @@gallifrey = :gallifrey
6
+ $the_doctor = :the_doctor
7
+
8
+ def get_binding
9
+ amelia_pond = :amelia_pond
10
+ @river_song = :river_song
11
+
12
+ return binding
13
+ end
14
+ end
15
+
16
+ describe DebugBar::Ext::Binding do
17
+
18
+ describe '[] method' do
19
+
20
+ before(:all) do
21
+ @binding = TestContext.new.get_binding
22
+ @binding.extend(DebugBar::Ext::Binding)
23
+ end
24
+
25
+ it 'should exist on binding' do
26
+ @binding.should respond_to(:[])
27
+ end
28
+
29
+ it 'should get local variables' do
30
+ @binding[:amelia_pond].should == :amelia_pond
31
+ @binding['amelia_pond'].should == :amelia_pond
32
+ end
33
+
34
+ it 'should get instance varaibles' do
35
+ @binding[:@river_song].should == :river_song
36
+ @binding['@river_song'].should == :river_song
37
+ end
38
+
39
+ it 'should get global variables' do
40
+ @binding[:$the_doctor].should == :the_doctor
41
+ @binding['$the_doctor'].should == :the_doctor
42
+ end
43
+
44
+ it 'should get constants' do
45
+ @binding[:TARDIS].should == :tardis
46
+ @binding['TARDIS'].should == :tardis
47
+ end
48
+
49
+ it 'should get class variables' do
50
+ @binding[:@@gallifrey].should == :gallifrey
51
+ @binding['@@gallifrey'].should == :gallifrey
52
+ end
53
+
54
+ it 'should not perform arbirary code' do
55
+ lambda {@binding['1+1']}.should raise_error(NameError)
56
+ lambda {@binding['amelia_pond.class']}.should raise_error(NameError)
57
+ lambda {@binding['def foo']}.should raise_error(NameError)
58
+ end
59
+
60
+ end
61
+
62
+ end