noodall-ui 0.0.11 → 0.0.12

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,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
+