noodall-ui 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,565 @@
1
+ begin
2
+ require 'cucumber/formatter/ordered_xml_markup'
3
+ require 'cucumber/formatter/duration'
4
+ require 'cucumber/formatter/io'
5
+ rescue LoadError
6
+ puts "Can't load SevenFeatures"
7
+ end
8
+
9
+ module Cucumber
10
+ module Formatter
11
+ class SevenFeatures
12
+ begin
13
+ include ERB::Util # for the #h method
14
+ include Duration
15
+ include Io
16
+ rescue NameError
17
+ puts "Can't load SevenFeatures"
18
+ end
19
+
20
+ def initialize(step_mother, path_or_io, options)
21
+ @io = ensure_io(path_or_io, "html")
22
+ @step_mother = step_mother
23
+ @options = options
24
+ @buffer = {}
25
+ @builder = create_builder(@io)
26
+ @feature_number = 0
27
+ @scenario_number = 0
28
+ @step_number = 0
29
+ @header_red = nil
30
+ end
31
+
32
+ def embed(file, mime_type)
33
+ case(mime_type)
34
+ when /^image\/(png|gif|jpg|jpeg)/
35
+ embed_image(file)
36
+ end
37
+ end
38
+
39
+ def embed_image(file)
40
+ id = file.hash
41
+ @builder.span(:class => 'embed') do |pre|
42
+ pre << %{<a href="" onclick="img=document.getElementById('#{id}'); img.style.display = (img.style.display == 'none' ? 'block' : 'none');return false">Screenshot</a><br>&nbsp;
43
+ <img id="#{id}" style="display: none" src="#{file}"/>}
44
+ end
45
+ end
46
+
47
+
48
+ def before_features(features)
49
+ @step_count = get_step_count(features)
50
+
51
+ # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
52
+ @builder.declare!(
53
+ :DOCTYPE,
54
+ :html,
55
+ :PUBLIC,
56
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
57
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
58
+ )
59
+
60
+ @builder << '<html xmlns ="http://www.w3.org/1999/xhtml">'
61
+ @builder.head do
62
+ @builder.meta(:content => 'text/html;charset=utf-8')
63
+ @builder.title 'Cucumber: ' + File.basename(Dir.getwd)
64
+ inline_css
65
+ google_js
66
+ #inline_js
67
+ end
68
+ @builder << '<body>'
69
+ @builder << "<!-- Step count #{@step_count}-->"
70
+ @builder << '<div class="cucumber">'
71
+ @builder.div(:id => 'cucumber-header') do
72
+ @builder.div(:id => 'label') do
73
+ @builder.h1('Cucumber: ' + File.basename(Dir.getwd))
74
+ end
75
+ @builder.div(:id => 'summary') do
76
+ @builder.p('',:id => 'totals')
77
+ @builder.p('',:id => 'duration')
78
+ @builder.div(:id => 'expand-collapse') do
79
+ @builder.p('Collapse\Expand All', :id => 'collapser')
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ def after_features(features)
86
+ print_stats(features)
87
+ @builder << '</div>'
88
+ @builder << '</body>'
89
+ @builder << '</html>'
90
+ end
91
+
92
+ def before_feature(feature)
93
+ @exceptions = []
94
+ @builder << '<div class="feature collapsed">'
95
+ end
96
+
97
+ def after_feature(feature)
98
+ @builder << '</div>'
99
+ end
100
+
101
+ def before_comment(comment)
102
+ @builder << '<pre class="comment">'
103
+ end
104
+
105
+ def after_comment(comment)
106
+ @builder << '</pre>'
107
+ end
108
+
109
+ def comment_line(comment_line)
110
+ @builder.text!(comment_line)
111
+ @builder.br
112
+ end
113
+
114
+ def after_tags(tags)
115
+ @tag_spacer = nil
116
+ end
117
+
118
+ def tag_name(tag_name)
119
+ @current_tag = tag_name
120
+ end
121
+
122
+ def feature_name(keyword, name)
123
+ lines = name.split(/\r?\n/)
124
+ return if lines.empty?
125
+ @builder.h2 do |h2|
126
+ @builder.span(lines[0], :class => 'val')
127
+ end
128
+ @builder.p(:class => 'narrative') do
129
+ lines[1..-1].each do |line|
130
+ @builder.text!(line.strip)
131
+ @builder.br
132
+ end
133
+ end
134
+ end
135
+
136
+ def before_background(background)
137
+ @in_background = true
138
+ @builder << '<div class="background">'
139
+ end
140
+
141
+ def after_background(background)
142
+ @in_background = nil
143
+ @builder << '</div>'
144
+ end
145
+
146
+ def background_name(keyword, name, file_colon_line, source_indent)
147
+ @listing_background = true
148
+ @builder.h3 do |h3|
149
+ @builder.span(keyword, :class => 'keyword')
150
+ @builder.text!(' ')
151
+ @builder.span(name, :class => 'val')
152
+ end
153
+ end
154
+
155
+ def before_feature_element(feature_element)
156
+ @scenario_number+=1
157
+ @scenario_red = false
158
+ css_class = {
159
+ Ast::Scenario => 'scenario collapsed',
160
+ Ast::ScenarioOutline => 'scenario outline'
161
+ }[feature_element.class]
162
+ @builder << "<div class='#{css_class}'>"
163
+ end
164
+
165
+ def after_feature_element(feature_element)
166
+ @builder << '</div>'
167
+ @open_step_list = true
168
+ end
169
+
170
+ def scenario_name(keyword, name, file_colon_line, source_indent)
171
+ @listing_background = false
172
+ @builder.h3(:id => "scenario_#{@scenario_number}") do
173
+ unless @current_tag.nil?
174
+ @builder.span(@current_tag, :class => 'tag')
175
+ @builder.text!(' ')
176
+ @current_tag = nil
177
+ end
178
+ #@builder.span(keyword, :class => 'keyword')
179
+ @builder.text!(' ')
180
+ @builder.span(name, :class => 'val')
181
+ end
182
+ end
183
+
184
+ def before_outline_table(outline_table)
185
+ @outline_row = 0
186
+ @builder << '<table>'
187
+ end
188
+
189
+ def after_outline_table(outline_table)
190
+ @builder << '</table>'
191
+ @outline_row = nil
192
+ end
193
+
194
+ def before_examples(examples)
195
+ @builder << '<div class="examples">'
196
+ end
197
+
198
+ def after_examples(examples)
199
+ @builder << '</div>'
200
+ end
201
+
202
+ def examples_name(keyword, name)
203
+ @builder.h4 do
204
+ @builder.span(keyword, :class => 'keyword')
205
+ @builder.text!(' ')
206
+ @builder.span(name, :class => 'val')
207
+ end
208
+ end
209
+
210
+ def before_steps(steps)
211
+ @builder << '<ol>'
212
+ end
213
+
214
+ def after_steps(steps)
215
+ @builder << '</ol>'
216
+ end
217
+
218
+ def before_step(step)
219
+ @step_id = step.dom_id
220
+ @step_number += 1
221
+ @step = step
222
+ end
223
+
224
+ def after_step(step)
225
+ move_progress
226
+ end
227
+
228
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
229
+ @step_match = step_match
230
+ @hide_this_step = false
231
+ if exception
232
+ if @exceptions.include?(exception)
233
+ @hide_this_step = true
234
+ return
235
+ end
236
+ @exceptions << exception
237
+ end
238
+ if status != :failed && @in_background ^ background
239
+ @hide_this_step = true
240
+ return
241
+ end
242
+ @status = status
243
+ return if @hide_this_step
244
+ set_scenario_color(status)
245
+ @builder << "<li id='#{@step_id}' class='step #{status}'>"
246
+ end
247
+
248
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
249
+ return if @hide_this_step
250
+ @builder << '</li>'
251
+ end
252
+
253
+ def step_name(keyword, step_match, status, source_indent, background)
254
+ @step_matches ||= []
255
+ background_in_scenario = background && !@listing_background
256
+ @skip_step = @step_matches.index(step_match) || background_in_scenario
257
+ @step_matches << step_match
258
+
259
+ unless @skip_step
260
+ build_step(keyword, step_match, status)
261
+ end
262
+ end
263
+
264
+ def exception(exception, status)
265
+ # build_exception_detail(exception)
266
+ end
267
+
268
+ def extra_failure_content(file_colon_line)
269
+ @snippet_extractor ||= SnippetExtractor.new
270
+ "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
271
+ end
272
+
273
+ def before_multiline_arg(multiline_arg)
274
+ return if @hide_this_step || @skip_step
275
+ if Ast::Table === multiline_arg
276
+ @builder << '<table>'
277
+ end
278
+ end
279
+
280
+ def after_multiline_arg(multiline_arg)
281
+ return if @hide_this_step || @skip_step
282
+ if Ast::Table === multiline_arg
283
+ @builder << '</table>'
284
+ end
285
+ end
286
+
287
+ def py_string(string)
288
+ return if @hide_this_step
289
+ @builder.pre(:class => 'val') do |pre|
290
+ @builder << string.gsub("\n", '&#x000A;')
291
+ end
292
+ end
293
+
294
+
295
+ def before_table_row(table_row)
296
+ @row_id = table_row.dom_id
297
+ @col_index = 0
298
+ return if @hide_this_step
299
+ @builder << "<tr class='step' id='#{@row_id}'>"
300
+ end
301
+
302
+ def after_table_row(table_row)
303
+ return if @hide_this_step
304
+ @builder << '</tr>'
305
+ if table_row.exception
306
+ @builder.tr do
307
+ @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
308
+ end
309
+ end
310
+ end
311
+ if @outline_row
312
+ @outline_row += 1
313
+ end
314
+ @step_number += 1
315
+ move_progress
316
+ end
317
+
318
+ def table_cell_value(value, status)
319
+ return if @hide_this_step
320
+
321
+ @cell_type = @outline_row == 0 ? :th : :td
322
+ attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
323
+ attributes[:class] += " #{status}" if status
324
+ build_cell(@cell_type, value, attributes)
325
+ set_scenario_color(status)
326
+ @col_index += 1
327
+ end
328
+
329
+ def announce(announcement)
330
+ @builder.pre(announcement, :class => 'announcement')
331
+ end
332
+
333
+ protected
334
+
335
+ def set_scenario_color(status)
336
+ if status == :undefined || status == :pending || status == :skipped
337
+ @builder.script do
338
+ @builder.text!("makeBlack('cucumber-header');") unless @header_red
339
+ @builder.text!("makeBlack('scenario_#{@scenario_number}');") unless @scenario_red
340
+ end
341
+ end
342
+
343
+ if status == :failed
344
+ @builder.script do
345
+ @builder.text!("makeRed('cucumber-header');") unless @header_red
346
+ @header_red = true
347
+ @builder.text!("makeRed('scenario_#{@scenario_number}');") unless @scenario_red
348
+ @scenario_red = true
349
+ end
350
+ end
351
+ end
352
+
353
+ def get_step_count(features)
354
+ count = 0
355
+ features = features.instance_variable_get("@features")
356
+ features.each do |feature|
357
+ #get background steps
358
+ if feature.instance_variable_get("@background")
359
+ background = feature.instance_variable_get("@background").instance_variable_get("@steps").instance_variable_get("@steps")
360
+ count += background.size unless background.nil?
361
+ end
362
+ #get scenarios
363
+ feature.instance_variable_get("@feature_elements").each do |scenario|
364
+ #get steps
365
+ steps = scenario.instance_variable_get("@steps").instance_variable_get("@steps")
366
+ unless steps.nil?
367
+ count += steps.size unless steps.nil?
368
+
369
+ #get example table
370
+ examples = scenario.instance_variable_get("@examples_array")
371
+ unless examples.nil?
372
+ examples.each do |example|
373
+ example_matrix = example.instance_variable_get("@outline_table").instance_variable_get("@cell_matrix")
374
+ count += example_matrix.size unless example_matrix.nil?
375
+ end
376
+ end
377
+
378
+ #get multiline step tables
379
+ steps.each do |step|
380
+ multi_arg = step.instance_variable_get("@multiline_arg")
381
+ next if multi_arg.nil?
382
+ matrix = multi_arg.instance_variable_get("@cell_matrix")
383
+ count += matrix.size unless matrix.nil?
384
+ end
385
+ end
386
+ end
387
+ end
388
+ return count
389
+ end
390
+
391
+ def build_step(keyword, step_match, status)
392
+ step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
393
+ @builder.div do |div|
394
+ @builder.span(keyword, :class => 'keyword')
395
+ @builder.text!(' ')
396
+ @builder.span(:class => 'step val') do |name|
397
+ name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
398
+ end
399
+ end
400
+ end
401
+
402
+ def build_cell(cell_type, value, attributes)
403
+ @builder.__send__(cell_type, attributes) do
404
+ @builder.div do
405
+ @builder.span(value,:class => 'step param')
406
+ end
407
+ end
408
+ end
409
+
410
+ def inline_css
411
+ @builder.style(:type => 'text/css') do
412
+ @builder << File.read(File.dirname(__FILE__) + '/cucumber.css')
413
+ end
414
+ end
415
+
416
+ def google_js
417
+ @builder.script(:type => 'text/javascript', :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js') do
418
+ @builder << ' '
419
+ end
420
+ end
421
+
422
+ def inline_js
423
+ @builder.script(:type => 'text/javascript') do
424
+ @builder << inline_js_content
425
+ end
426
+ end
427
+
428
+ def inline_js_content
429
+ <<-EOF
430
+ function moveProgressBar(percentDone) {
431
+ document.getElementById("cucumber-header").style.width = percentDone +"%";
432
+ }
433
+ function makeRed(element_id) {
434
+ document.getElementById(element_id).style.color = '#C40D0D';
435
+ }
436
+
437
+ function makeBlack(element_id) {
438
+ document.getElementById(element_id).style.background = '#FFFFFF';
439
+ document.getElementById(element_id).style.color = '#000000';
440
+ }
441
+
442
+ $(document).ready(function() {
443
+ $('.feature h2').click(function() {
444
+ $(this).parent('.feature').toggleClass('collapsed');
445
+ });
446
+
447
+
448
+ $("#collapser").css('cursor', 'pointer');
449
+ $("#collapser").click(function() {
450
+ $('.feature').toggleClass('collapsed');
451
+ $('.scenario').toggleClass('collapsed');
452
+ });
453
+
454
+ $('.scenario h3').click(function() {
455
+ $(this).parent('.scenario').toggleClass('collapsed');
456
+ });
457
+ });
458
+
459
+ EOF
460
+ end
461
+
462
+ def move_progress
463
+ @builder << " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
464
+ end
465
+
466
+ def percent_done
467
+ result = 100.0
468
+ if @step_count != 0
469
+ result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
470
+ end
471
+ result
472
+ end
473
+
474
+ def backtrace_line(line)
475
+ line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*)/) do
476
+ if ENV['TM_PROJECT_DIRECTORY']
477
+ "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
478
+ else
479
+ line
480
+ end
481
+ end
482
+ end
483
+
484
+ def print_stats(features)
485
+ @builder << "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in #{format_duration(features.duration)} seconds\";</script>"
486
+ @builder << "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
487
+ end
488
+
489
+ def print_stat_string(features)
490
+ string = String.new
491
+ string << dump_count(@step_mother.scenarios.length, "scenario")
492
+ scenario_count = print_status_counts{|status| @step_mother.scenarios(status)}
493
+ string << scenario_count if scenario_count
494
+ string << "<br />"
495
+ string << dump_count(@step_mother.steps.length, "step")
496
+ step_count = print_status_counts{|status| @step_mother.steps(status)}
497
+ string << step_count if step_count
498
+ end
499
+
500
+ def print_status_counts
501
+ counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
502
+ elements = yield status
503
+ elements.any? ? "#{elements.length} #{status.to_s}" : nil
504
+ end.compact
505
+ return " (#{counts.join(', ')})" if counts.any?
506
+ end
507
+
508
+ def dump_count(count, what, state=nil)
509
+ [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ")
510
+ end
511
+
512
+ def create_builder(io)
513
+ OrderedXmlMarkup.new(:target => io, :indent => 0)
514
+ end
515
+ end
516
+ end
517
+ end
518
+
519
+
520
+ class SnippetExtractor #:nodoc:
521
+ class NullConverter; def convert(code, pre); code; end; end #:nodoc:
522
+ begin; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end
523
+
524
+ def snippet(error)
525
+ raw_code, line = snippet_for(error[0])
526
+ highlighted = @@converter.convert(raw_code, false)
527
+ highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
528
+ post_process(highlighted, line)
529
+ end
530
+
531
+ def snippet_for(error_line)
532
+ if error_line =~ /(.*):(\d+)/
533
+ file = $1
534
+ line = $2.to_i
535
+ [lines_around(file, line), line]
536
+ else
537
+ return snippet_for()
538
+ ["# Couldn't get snippet for #{error_line}", 1]
539
+ end
540
+ end
541
+
542
+ def lines_around(file, line)
543
+ if File.file?(file)
544
+ lines = File.open(file).read.split("\n")
545
+ min = [0, line-3].max
546
+ max = [line+1, lines.length-1].min
547
+ selected_lines = []
548
+ selected_lines.join("\n")
549
+ lines[min..max].join("\n")
550
+ else
551
+ "# Couldn't get snippet for #{file}"
552
+ end
553
+ end
554
+
555
+ def post_process(highlighted, offending_line)
556
+ new_lines = []
557
+ highlighted.split("\n").each_with_index do |line, i|
558
+ new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
559
+ new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
560
+ new_lines << new_line
561
+ end
562
+ new_lines.join("\n")
563
+ end
564
+
565
+ end
@@ -1,5 +1,5 @@
1
1
  module Noodall
2
2
  module UI
3
- VERSION = "0.0.11"
3
+ VERSION = "0.0.12"
4
4
  end
5
5
  end
data/public/404.html ADDED
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
27
+