cucumber 0.4.4 → 0.4.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.gitignore +2 -1
  2. data/Caliper.yml +5 -0
  3. data/History.txt +24 -1
  4. data/Rakefile +1 -1
  5. data/VERSION.yml +2 -2
  6. data/cucumber.gemspec +18 -27
  7. data/cucumber.yml +10 -4
  8. data/examples/i18n/README.textile +1 -1
  9. data/examples/i18n/bg/features/consecutive_calculations.feature +1 -1
  10. data/examples/i18n/bg/features/division.feature +1 -1
  11. data/examples/i18n/en-lol/Rakefile +1 -3
  12. data/examples/i18n/en/features/division.feature +4 -4
  13. data/examples/i18n/et/features/jagamine.feature +1 -1
  14. data/examples/i18n/fr/features/addition2.feature +17 -0
  15. data/examples/i18n/fr/features/step_definitions/calculatrice_steps.rb +13 -0
  16. data/examples/i18n/ja/features/step_definitons/calculator_steps.rb +3 -3
  17. data/examples/i18n/lt/features/addition.feature +2 -2
  18. data/examples/i18n/lt/features/division.feature +1 -1
  19. data/examples/i18n/zh-CN/Rakefile +1 -3
  20. data/examples/i18n/zh-TW/Rakefile +1 -3
  21. data/examples/tcl/README.textile +11 -0
  22. data/examples/tcl/Rakefile +6 -0
  23. data/examples/tcl/features/fibonnacci.feature +17 -0
  24. data/examples/tcl/features/step_definitions/fib_steps.rb +7 -0
  25. data/examples/tcl/features/support/env.rb +6 -0
  26. data/examples/tcl/src/fib.tcl +3 -0
  27. data/examples/tickets/features/177/1.feature +3 -4
  28. data/examples/tickets/features/177/2.feature +2 -3
  29. data/examples/tickets/features/241.feature +2 -3
  30. data/examples/tickets/features/301/filter_background_tagged_hooks.feature +1 -1
  31. data/features/cucumber_cli.feature +4 -2
  32. data/features/custom_formatter.feature +32 -31
  33. data/features/html_formatter/a.html +319 -102
  34. data/features/language_help.feature +12 -14
  35. data/features/support/env.rb +1 -1
  36. data/features/transform.feature +17 -3
  37. data/gem_tasks/features.rake +3 -1
  38. data/lib/cucumber/ast/scenario_outline.rb +1 -1
  39. data/lib/cucumber/ast/step_invocation.rb +2 -2
  40. data/lib/cucumber/cli/configuration.rb +2 -17
  41. data/lib/cucumber/cli/language_help_formatter.rb +1 -1
  42. data/lib/cucumber/cli/options.rb +5 -4
  43. data/lib/cucumber/formatter/cucumber.css +198 -82
  44. data/lib/cucumber/formatter/cucumber.sass +173 -71
  45. data/lib/cucumber/formatter/html.rb +371 -195
  46. data/lib/cucumber/formatter/io.rb +33 -0
  47. data/lib/cucumber/formatter/junit.rb +16 -7
  48. data/lib/cucumber/formatter/pdf.rb +5 -5
  49. data/lib/cucumber/formatter/pretty.rb +6 -8
  50. data/lib/cucumber/formatter/progress.rb +4 -2
  51. data/lib/cucumber/formatter/rerun.rb +4 -2
  52. data/lib/cucumber/formatter/steps.rb +2 -2
  53. data/lib/cucumber/formatter/tag_cloud.rb +6 -2
  54. data/lib/cucumber/formatter/usage.rb +2 -2
  55. data/lib/cucumber/languages.yml +205 -281
  56. data/lib/cucumber/parser/feature.rb +348 -82
  57. data/lib/cucumber/parser/feature.tt +47 -11
  58. data/lib/cucumber/parser/i18n.tt +7 -17
  59. data/lib/cucumber/parser/natural_language.rb +45 -18
  60. data/lib/cucumber/rb_support/rb_transform.rb +1 -1
  61. data/lib/cucumber/step_mother.rb +2 -3
  62. data/lib/cucumber/webrat/element_locator.rb +9 -5
  63. data/spec/cucumber/cli/options_spec.rb +0 -3
  64. data/spec/cucumber/formatter/html_spec.rb +15 -2
  65. data/spec/cucumber/formatter/junit_spec.rb +3 -0
  66. data/spec/cucumber/treetop_parser/fit_scenario.feature +2 -2
  67. data/spec/cucumber/treetop_parser/given_scenario.feature +1 -1
  68. data/spec/cucumber/treetop_parser/multiple_tables.feature +2 -2
  69. data/spec/cucumber/treetop_parser/test_dos.feature +1 -1
  70. metadata +17 -26
  71. data/Manifest.txt +0 -0
  72. data/lib/cucumber/rails/action_controller.rb +0 -38
  73. data/lib/cucumber/rails/active_record.rb +0 -30
  74. data/lib/cucumber/rails/rspec.rb +0 -10
  75. data/lib/cucumber/rails/test_unit.rb +0 -9
  76. data/lib/cucumber/rails/world.rb +0 -30
  77. data/rails_generators/cucumber/USAGE +0 -11
  78. data/rails_generators/cucumber/cucumber_generator.rb +0 -117
  79. data/rails_generators/cucumber/templates/cucumber +0 -17
  80. data/rails_generators/cucumber/templates/cucumber.rake +0 -46
  81. data/rails_generators/cucumber/templates/cucumber_environment.rb +0 -30
  82. data/rails_generators/cucumber/templates/env.rb +0 -49
  83. data/rails_generators/cucumber/templates/paths.rb +0 -27
  84. data/rails_generators/cucumber/templates/spork_env.rb +0 -57
  85. data/rails_generators/cucumber/templates/version_check.rb +0 -31
  86. data/rails_generators/cucumber/templates/webrat_steps.rb +0 -241
  87. data/rails_generators/feature/USAGE +0 -12
  88. data/rails_generators/feature/feature_generator.rb +0 -40
  89. data/rails_generators/feature/templates/feature.erb +0 -31
  90. data/rails_generators/feature/templates/steps.erb +0 -14
@@ -1,49 +1,70 @@
1
- # cucumber.css is generated from cucumber.sass
2
- # Regenerate with rake sass
1
+ /* cucumber.css is generated from cucumber.sass */
2
+ /* Regenerate with rake sass */
3
3
 
4
- !step_left = 5px solid
5
- !step_bottom = 1px solid
4
+ !step_left = 5px "solid"
5
+ !step_bottom = 1px "solid"
6
6
 
7
- !failed = #FFC0CB
8
- !failed_dark = #FF0000
9
- !failed_text = #8B0000
7
+ !failed = #FFFBD3
8
+ !failed_border = #C20000
9
+ !failed_text = #C20000
10
10
 
11
- !passed = #98FB98
12
- !passed_dark = #00FF00
13
- !passed_text = #001111
11
+ !passed = #DBFFB4
12
+ !passed_border = #65C400
13
+ !passed_text = #3D7700
14
14
 
15
- !skipped = #E0FFFF
16
- !skipped_dark = #00FFFF
17
- !skipped_text = #001111
15
+ !skipped = #E0FFFF
16
+ !skipped_border = #00FFFF
17
+ !skipped_text = #001111
18
18
 
19
- !pending = #FFFF00
20
- !pending_dark = #FF8000
21
- !pending_text = #2A1B0A
19
+ !pending = #FCFB98
20
+ !pending_border = #FAF834
21
+ !pending_text = #131313
22
22
 
23
- !undefined = #FFFF00
24
- !undefined_dark = #FF8000
25
- !undefined_text = #2A1B0A
23
+ !undefined = #FCFB98
24
+ !undefined_border = #FAF834
25
+ !undefined_text = #131313
26
26
 
27
- .cucumber
28
- :font-family "Lucida Grande", Helvetica, sans-serif
29
- :font-size 0.7em
27
+ body
28
+ :font-size 0px
29
+ :color #FFFFFF
30
+ :margin 0px
31
+ :padding 0px
32
+
33
+ .cucumber,td,th
34
+ :font normal 11px "Lucida Grande", Helvetica, sans-serif
30
35
  :background white
31
- :padding 1em
36
+ :color #000000
32
37
 
33
- h1, h2, h3, h4, ol, li, pre, p
34
- :font-size 1em
35
- :padding 0px
36
- :margin 0px
38
+ #cucumber-header
39
+ :background #65C400
40
+ :color #FFFFFF
41
+ :height 5em
42
+
43
+ .scenario
44
+ h3
45
+ :font-size 11px
46
+ :padding 3px
47
+ :margin 0
48
+ :background #65C400
49
+ :color #FFFFFF
50
+ :font-weight bold
51
+
52
+ h1
53
+ :margin 0px 10px 0px 10px
54
+ :padding 10px
55
+ :font-family "Lucida Grande", Helvetica, sans-serif
56
+ :font-size 1.8em
57
+ :position absolute
58
+
59
+ h4
60
+ :margin-bottom 2px
37
61
 
38
62
  div.feature
39
- :border 1px solid
40
63
  :padding 2px
41
- :margin 4px
64
+ :margin 0px 10px 5px 10px
42
65
 
43
- div.background, div.scenario, p
44
- :padding 0em 0em 0em 1em
45
- div.examples
46
- :padding 0em 0em 0em 1em
66
+ div.examples
67
+ :padding 0em 0em 0em 1em
47
68
 
48
69
  .stats
49
70
  :margin 2em
@@ -53,14 +74,19 @@
53
74
  li
54
75
  :display inline
55
76
 
77
+ .tag
78
+ :font-weight bold
79
+ :color #246AC1
80
+
56
81
  .backtrace
57
82
  :margin-top 0
58
83
  :margin-bottom 0
59
84
  :margin-left 1em
85
+ :color #000000
60
86
 
61
87
  a
62
88
  :text-decoration none
63
- :color inherit
89
+ :color #BE5C00
64
90
 
65
91
  &:hover
66
92
  :text-decoration underline
@@ -68,72 +94,148 @@
68
94
  :font-weight normal
69
95
 
70
96
  div.examples
71
- :border 1px solid
72
- :padding 2px
73
- :margin 4px
97
+ :margin 5px 0px 5px 15px
98
+ :color #000000
99
+
100
+ .outline
101
+ table
102
+ :margin 0px 0px 5px 10px
74
103
 
75
104
  table
76
105
  :border-collapse collapse
77
-
78
- td, th
79
- :font-size 0.7em
80
- :border 1px solid #AAAAAA
106
+ td
107
+ :padding 3px 3px 3px 5px
108
+ td.failed, td.passed, td.skipped, td.pending, td.undefined
109
+ :padding-left 18px
110
+ :padding-right 10px
81
111
  td.failed
82
- :background #FFC0CB
83
- :color #8B0000
112
+ :border-left= !step_left !failed_border
113
+ :border-bottom= !step_bottom !failed_border
114
+ :background= !failed
115
+ :color= !failed_text
84
116
  td.passed
85
- :background #98FB98
117
+ :border-left= !step_left !passed_border
118
+ :border-bottom= !step_bottom !passed_border
119
+ :background= !passed
86
120
  :color= !passed_text
87
121
  td.skipped
122
+ :border-left= !step_left !skipped_border
123
+ :border-bottom= !step_bottom !skipped_border
88
124
  :background= !skipped
89
125
  :color= !skipped_text
90
126
  td.pending
91
- :background #FFFFE0
92
- :color #111100
127
+ :border-left= !step_left !pending_border
128
+ :border-bottom= !step_bottom !pending_border
129
+ :background= !pending
130
+ :color= !pending_text
93
131
  td.undefined
94
- :background #FFFFE0
95
- :color #111100
132
+ :border-left= !step_left !undefined_border
133
+ :border-bottom= !step_bottom !undefined_border
134
+ :background= !undefined
135
+ :color= !undefined_text
96
136
 
97
137
  ol
98
138
  :list-style none
99
-
139
+ :margin 0px
140
+ :padding 0px
141
+
142
+ li.step
143
+ :padding 3px 3px 3px 18px
144
+ :margin 5px 0px 5px 5px
100
145
  li
101
146
  :margin 0em 0em 0em 1em
102
- :padding 0em 0em 0em 0.2em
147
+ :padding 0em 0em 0em 0.2em
103
148
  span.param
104
- :font-weight bold
149
+ :font-weight bold
105
150
  li.failed
106
- :border-left= !step_left !failed_dark
107
- :border-bottom= !step_bottom !failed_dark
151
+ :border-left= !step_left !failed_border
152
+ :border-bottom= !step_bottom !failed_border
108
153
  :background= !failed
109
154
  :color= !failed_text
110
- span.param
111
- :background !failed_dark
112
155
  li.passed
113
- :border-left= !step_left !passed_dark
114
- :border-bottom= !step_bottom !passed_dark
156
+ :border-left= !step_left !passed_border
157
+ :border-bottom= !step_bottom !passed_border
115
158
  :background= !passed
116
159
  :color= !passed_text
117
- span.param
118
- :background= !passed_dark
119
160
  li.skipped
120
- :border-left= !step_left !skipped_dark
121
- :border-bottom= !step_bottom !skipped_dark
161
+ :border-left= !step_left !skipped_border
162
+ :border-bottom= !step_bottom !skipped_border
122
163
  :background= !skipped
123
164
  :color= !skipped_text
124
- span.param
125
- :background= !skipped_dark
126
165
  li.pending
127
- :border-left= !step_left !pending_dark
128
- :border-bottom= !step_bottom !pending_dark
166
+ :border-left= !step_left !pending_border
167
+ :border-bottom= !step_bottom !pending_border
129
168
  :background= !pending
130
169
  :color= !pending_text
131
- span.param
132
- :background= !pending_dark
133
170
  li.undefined
134
- :border-left= !step_left !undefined_dark
135
- :border-bottom= !step_bottom !undefined_dark
171
+ :border-left= !step_left !undefined_border
172
+ :border-bottom= !step_bottom !undefined_border
136
173
  :background= !undefined
137
174
  :color= !undefined_text
138
- span.param
139
- :background= !undefined_dark
175
+
176
+ #summary
177
+ :margin 0px
178
+ :padding 5px 10px
179
+ :text-align right
180
+ :top 0px
181
+ :right 0px
182
+ :float right
183
+
184
+ p
185
+ :margin 0 0 0 2px
186
+
187
+ #totals
188
+ :font-size 1.2em
189
+
190
+ .ruby
191
+ :font-size 12px
192
+ :font-family monospace
193
+ :color white
194
+ :background black
195
+ :padding 0.1em 0 0.2em 0
196
+
197
+ .keyword
198
+ :color #FF6600
199
+ .constant
200
+ :color #339999
201
+ .attribute
202
+ :color white
203
+ .global
204
+ :color white
205
+ .module
206
+ :color white
207
+ .class
208
+ :color white
209
+ .string
210
+ :color #66FF00
211
+ .ident
212
+ :color white
213
+ .method
214
+ :color #FFCC00
215
+ .number
216
+ :color white
217
+ .char
218
+ :color white
219
+ .comment
220
+ :color #9933CC
221
+ .symbol
222
+ :color white
223
+ .regex
224
+ :color #44B4CC
225
+ .punct
226
+ :color white
227
+ .escape
228
+ :color white
229
+ .interp
230
+ :color white
231
+ .expr
232
+ :color white
233
+
234
+ .offending
235
+ :background #333333
236
+ .linenum
237
+ :width 75px
238
+ :padding 0.1em 1em 0.2em 0
239
+ :color #000000
240
+ :background #FFFBD3
241
+
@@ -1,203 +1,201 @@
1
1
  require 'cucumber/formatter/ordered_xml_markup'
2
2
  require 'cucumber/formatter/duration'
3
- require 'cucumber/formatter/summary'
3
+ require 'cucumber/formatter/io'
4
4
 
5
5
  module Cucumber
6
6
  module Formatter
7
- # The formatter used for <tt>--format html</tt>
8
7
  class Html
9
8
  include ERB::Util # for the #h method
10
9
  include Duration
11
- include Summary
10
+ include Io
12
11
 
13
- def initialize(step_mother, io, options)
14
- @io = io
12
+ def initialize(step_mother, path_or_io, options)
13
+ @io = ensure_io(path_or_io, "html")
14
+ @step_mother = step_mother
15
15
  @options = options
16
16
  @buffer = {}
17
- @step_mother = step_mother
18
- @current_builder = create_builder(@io)
17
+ @builder = create_builder(@io)
18
+ @feature_number = 0
19
+ @scenario_number = 0
20
+ @step_number = 0
21
+ @header_red = nil
19
22
  end
20
-
23
+
21
24
  def before_features(features)
22
- start_buffering :features
23
- end
24
-
25
- def after_features(features)
26
- stop_buffering :features
25
+ @step_count = get_step_count(features)
26
+
27
27
  # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
28
- builder.declare!(
28
+ @builder.declare!(
29
29
  :DOCTYPE,
30
30
  :html,
31
31
  :PUBLIC,
32
- '-//W3C//DTD XHTML 1.0 Strict//EN',
32
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
33
33
  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
34
34
  )
35
- builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
36
- builder.head do
37
- builder.meta(:content => 'text/html;charset=utf-8')
38
- builder.title 'Cucumber'
39
- inline_css
35
+
36
+ @builder << '<html xmlns ="http://www.w3.org/1999/xhtml">'
37
+ @builder.head do
38
+ @builder.meta(:content => 'text/html;charset=utf-8')
39
+ @builder.title 'Cucumber'
40
+ inline_css
41
+ inline_js
42
+ end
43
+ @builder << '<body>'
44
+ @builder << "<!-- Step count #{@step_count}-->"
45
+ @builder << '<div class="cucumber">'
46
+ @builder.div(:id => 'cucumber-header') do
47
+ @builder.div(:id => 'label') do
48
+ @builder.h1('Cucumber Features')
40
49
  end
41
- builder.body do
42
- builder.div(:class => 'cucumber') do
43
- builder << buffer(:features)
44
- builder.div(scenario_summary(@step_mother) {|status_count, _| status_count}, :class => 'summary')
45
- builder.div(step_summary(@step_mother) {|status_count, _| status_count}, :class => 'summary')
46
- builder.div(format_duration(features.duration), :class => 'duration')
47
- end
50
+ @builder.div(:id => 'summary') do
51
+ @builder.p('',:id => 'totals')
52
+ @builder.p('',:id => 'duration')
48
53
  end
49
54
  end
50
55
  end
51
-
56
+
57
+ def after_features(features)
58
+ print_stats(features)
59
+ @builder << '</div>'
60
+ @builder << '</body>'
61
+ @builder << '</html>'
62
+ end
63
+
52
64
  def before_feature(feature)
53
- start_buffering :feature
54
65
  @exceptions = []
66
+ @builder << '<div class="feature">'
55
67
  end
56
-
68
+
57
69
  def after_feature(feature)
58
- stop_buffering :feature
59
- builder.div(:class => 'feature') do
60
- builder << buffer(:feature)
61
- end
70
+ @builder << '</div>'
62
71
  end
63
-
72
+
64
73
  def before_comment(comment)
65
- start_buffering :comment
74
+ @builder << '<pre class="comment">'
66
75
  end
67
76
 
68
77
  def after_comment(comment)
69
- stop_buffering :comment
70
- builder.pre(:class => 'comment') do
71
- builder << buffer(:comment)
72
- end
78
+ @builder << '</pre>'
73
79
  end
74
-
80
+
75
81
  def comment_line(comment_line)
76
- builder.text!(comment_line)
77
- builder.br
82
+ @builder.text!(comment_line)
83
+ @builder.br
78
84
  end
79
-
85
+
80
86
  def after_tags(tags)
81
87
  @tag_spacer = nil
82
88
  end
83
-
89
+
84
90
  def tag_name(tag_name)
85
- builder.text!(@tag_spacer) if @tag_spacer
91
+ @builder.text!(@tag_spacer) if @tag_spacer
86
92
  @tag_spacer = ' '
87
- builder.span(tag_name, :class => 'tag')
93
+ @builder.span(tag_name, :class => 'tag')
88
94
  end
89
-
95
+
90
96
  def feature_name(name)
91
97
  lines = name.split(/\r?\n/)
92
98
  return if lines.empty?
93
- builder.h2 do |h2|
94
- builder.span(lines[0], :class => 'val')
99
+ @builder.h2 do |h2|
100
+ @builder.span(lines[0], :class => 'val')
95
101
  end
96
- builder.p(:class => 'narrative') do
102
+ @builder.p(:class => 'narrative') do
97
103
  lines[1..-1].each do |line|
98
- builder.text!(line.strip)
99
- builder.br
104
+ @builder.text!(line.strip)
105
+ @builder.br
100
106
  end
101
107
  end
102
108
  end
103
-
109
+
104
110
  def before_background(background)
105
111
  @in_background = true
106
- start_buffering :background
112
+ @builder << '<div class="background">'
107
113
  end
108
-
114
+
109
115
  def after_background(background)
110
- stop_buffering :background
111
116
  @in_background = nil
112
- builder.div(:class => 'background') do
113
- builder << buffer(:background)
114
- end
117
+ @builder << '</div>'
115
118
  end
116
-
119
+
117
120
  def background_name(keyword, name, file_colon_line, source_indent)
118
121
  @listing_background = true
119
- builder.h3 do |h3|
120
- builder.span(keyword, :class => 'keyword')
121
- builder.text!(' ')
122
- builder.span(name, :class => 'val')
122
+ @builder.h3 do |h3|
123
+ @builder.span(keyword, :class => 'keyword')
124
+ @builder.text!(' ')
125
+ @builder.span(name, :class => 'val')
123
126
  end
124
127
  end
125
128
 
126
129
  def before_feature_element(feature_element)
127
- start_buffering :feature_element
128
- end
129
-
130
- def after_feature_element(feature_element)
131
- stop_buffering :feature_element
130
+ @scenario_number+=1
131
+ @scenario_red = false
132
132
  css_class = {
133
133
  Ast::Scenario => 'scenario',
134
134
  Ast::ScenarioOutline => 'scenario outline'
135
- }[feature_element.class]
135
+ }[feature_element.class]
136
+ @builder << "<div class='#{css_class}'>"
137
+ end
136
138
 
137
- builder.div(:class => css_class) do
138
- builder << buffer(:feature_element)
139
- end
139
+ def after_feature_element(feature_element)
140
+ @builder << '</div>'
140
141
  @open_step_list = true
141
142
  end
142
143
 
143
144
  def scenario_name(keyword, name, file_colon_line, source_indent)
144
145
  @listing_background = false
145
- builder.h3 do
146
- builder.span(keyword, :class => 'keyword')
147
- builder.text!(' ')
148
- builder.span(name, :class => 'val')
146
+ @builder.h3(:id => "scenario_#{@scenario_number}") do
147
+ @builder.span(keyword, :class => 'keyword')
148
+ @builder.text!(' ')
149
+ @builder.span(name, :class => 'val')
149
150
  end
150
151
  end
151
-
152
+
152
153
  def before_outline_table(outline_table)
153
154
  @outline_row = 0
154
- start_buffering :outline_table
155
+ @builder << '<table>'
155
156
  end
156
-
157
+
157
158
  def after_outline_table(outline_table)
158
- stop_buffering :outline_table
159
- builder.table do
160
- builder << buffer(:outline_table)
161
- end
159
+ @builder << '</table>'
162
160
  @outline_row = nil
163
161
  end
164
-
162
+
165
163
  def before_examples(examples)
166
- start_buffering :examples
164
+ @builder << '<div class="examples">'
167
165
  end
168
166
 
169
167
  def after_examples(examples)
170
- stop_buffering :examples
171
- builder.div(:class => 'examples') do
172
- builder << buffer(:examples)
173
- end
168
+ @builder << '</div>'
174
169
  end
175
170
 
176
171
  def examples_name(keyword, name)
177
- builder.h4 do
178
- builder.span(keyword, :class => 'keyword')
179
- builder.text!(' ')
180
- builder.span(name, :class => 'val')
172
+ @builder.h4 do
173
+ @builder.span(keyword, :class => 'keyword')
174
+ @builder.text!(' ')
175
+ @builder.span(name, :class => 'val')
181
176
  end
182
177
  end
183
-
178
+
184
179
  def before_steps(steps)
185
- start_buffering :steps
180
+ @builder << '<ol>'
186
181
  end
187
-
182
+
188
183
  def after_steps(steps)
189
- stop_buffering :steps
190
- builder.ol do
191
- builder << buffer(:steps)
192
- end
184
+ @builder << '</ol>'
193
185
  end
194
-
186
+
195
187
  def before_step(step)
196
188
  @step_id = step.dom_id
189
+ @step_number += 1
190
+ @step = step
191
+ end
192
+
193
+ def after_step(step)
194
+ move_progress
197
195
  end
198
196
 
199
197
  def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
200
- start_buffering :step_result
198
+ @step_match = step_match
201
199
  @hide_this_step = false
202
200
  if exception
203
201
  if @exceptions.include?(exception)
@@ -211,14 +209,21 @@ module Cucumber
211
209
  return
212
210
  end
213
211
  @status = status
212
+ return if @hide_this_step
213
+ set_scenario_color(status)
214
+ @builder << "<li id='#{@step_id}' class='step #{status}'>"
214
215
  end
215
-
216
+
216
217
  def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
217
- stop_buffering :step_result
218
218
  return if @hide_this_step
219
- builder.li(:id => @step_id, :class => "step #{status}") do
220
- builder << buffer(:step_result)
219
+ # print snippet for undefined steps
220
+ if status == :undefined
221
+ step_multiline_class = @step.multiline_arg ? @step.multiline_arg.class : nil
222
+ @builder.pre do |pre|
223
+ pre << @step_mother.snippet_text(keyword,step_match.instance_variable_get("@name") || '',step_multiline_class)
224
+ end
221
225
  end
226
+ @builder << '</li>'
222
227
  end
223
228
 
224
229
  def step_name(keyword, step_match, status, source_indent, background)
@@ -226,144 +231,315 @@ module Cucumber
226
231
  background_in_scenario = background && !@listing_background
227
232
  @skip_step = @step_matches.index(step_match) || background_in_scenario
228
233
  @step_matches << step_match
229
-
234
+
230
235
  unless @skip_step
231
236
  build_step(keyword, step_match, status)
232
237
  end
233
238
  end
234
239
 
235
240
  def exception(exception, status)
236
- return if @hide_this_step
237
- builder.pre(format_exception(exception), :class => status)
241
+ build_exception_detail(exception)
238
242
  end
239
-
240
- def before_multiline_arg(multiline_arg)
241
- start_buffering :multiline_arg
243
+
244
+ def extra_failure_content(file_colon_line)
245
+ @snippet_extractor ||= SnippetExtractor.new
246
+ "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
242
247
  end
243
248
 
249
+ def before_multiline_arg(multiline_arg)
250
+ return if @hide_this_step || @skip_step
251
+ if Ast::Table === multiline_arg
252
+ @builder << '<table>'
253
+ end
254
+ end
255
+
244
256
  def after_multiline_arg(multiline_arg)
245
- stop_buffering :multiline_arg
246
257
  return if @hide_this_step || @skip_step
247
258
  if Ast::Table === multiline_arg
248
- builder.table do
249
- builder << buffer(:multiline_arg)
250
- end
251
- else
252
- builder << buffer(:multiline_arg)
259
+ @builder << '</table>'
253
260
  end
254
261
  end
255
262
 
256
263
  def py_string(string)
257
264
  return if @hide_this_step
258
- builder.pre(:class => 'val') do |pre|
259
- builder << string.gsub("\n", '&#x000A;')
265
+ @builder.pre(:class => 'val') do |pre|
266
+ @builder << string.gsub("\n", '&#x000A;')
260
267
  end
261
268
  end
262
-
269
+
270
+
263
271
  def before_table_row(table_row)
264
272
  @row_id = table_row.dom_id
265
273
  @col_index = 0
266
- start_buffering :table_row
274
+ return if @hide_this_step
275
+ @builder << "<tr class='step' id='#{@row_id}'>"
267
276
  end
268
-
277
+
269
278
  def after_table_row(table_row)
270
- stop_buffering :table_row
271
279
  return if @hide_this_step
272
- builder.tr(:id => @row_id) do
273
- builder << buffer(:table_row)
274
- end
280
+ @builder << '</tr>'
275
281
  if table_row.exception
276
- builder.tr do
277
- builder.td(:colspan => @col_index.to_s, :class => 'failed') do
278
- builder.pre do |pre|
282
+ @builder.tr do
283
+ @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
284
+ @builder.pre do |pre|
279
285
  pre << format_exception(table_row.exception)
280
286
  end
281
287
  end
282
288
  end
283
289
  end
284
- @outline_row += 1 if @outline_row
290
+ if @outline_row
291
+ @outline_row += 1
292
+ end
293
+ @step_number += 1
294
+ move_progress
285
295
  end
286
296
 
287
297
  def table_cell_value(value, status)
288
298
  return if @hide_this_step
289
299
 
290
- cell_type = @outline_row == 0 ? :th : :td
291
- attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'val'}
300
+ @cell_type = @outline_row == 0 ? :th : :td
301
+ attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
292
302
  attributes[:class] += " #{status}" if status
293
- build_cell(cell_type, value, attributes)
303
+ build_cell(@cell_type, value, attributes)
304
+ set_scenario_color(status)
294
305
  @col_index += 1
295
306
  end
296
307
 
297
308
  def announce(announcement)
298
- builder.pre(announcement, :class => 'announcement')
309
+ @builder.pre(announcement, :class => 'announcement')
299
310
  end
300
311
 
301
- def embed(file, mime_type)
302
- case(mime_type)
303
- when /^image\/(png|gif|jpg)/
304
- embed_image(file)
312
+ protected
313
+
314
+ def build_exception_detail(exception)
315
+ backtrace = Array.new
316
+ @builder.div(:class => 'message') do
317
+ message = exception.message
318
+ if message.include?('Exception caught')
319
+ matches = message.match(/Showing <i>(.+)<\/i>(?:.+)#(\d+)/)
320
+ backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"]
321
+ message = message.match(/<code>([^(\/)]+)<\//m)[1]
322
+ end
323
+ @builder << "<pre>#{message}</pre>"
324
+ end
325
+ @builder.div(:class => 'backtrace') do
326
+ @builder.pre do
327
+ # backtrace += (exception.backtrace.size == 1 || exception.backtrace[0].include?('(eval):')) ? ["#{RAILS_ROOT}/#{@step_match.file_colon_line}"] + exception.backtrace : exception.backtrace
328
+ backtrace = exception.backtrace
329
+ backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
330
+ @builder << backtrace_line(backtrace.join("\n"))
331
+ end
332
+ end
333
+ extra = extra_failure_content(backtrace)
334
+ @builder << extra unless extra == ""
305
335
  end
306
- end
307
336
 
308
- private
309
-
310
- def embed_image(file)
311
- id = file.hash
312
- builder.pre(:class => 'embed') do |pre|
313
- pre << %{<a href="#" onclick="img=document.getElementById('#{id}'); img.style.display = (img.style.display == 'none' ? 'block' : 'none');">Screenshot</a>
314
- <img id="#{id}" style="display: none" src="#{file}" />}
337
+ def set_scenario_color(status)
338
+ if status == :undefined
339
+ @builder.script do
340
+ @builder.text!("makeYellow('cucumber-header');") unless @header_red
341
+ @builder.text!("makeYellow('scenario_#{@scenario_number}');") unless @scenario_red
342
+ end
343
+ end
344
+ if status == :failed
345
+ @builder.script do
346
+ @builder.text!("makeRed('cucumber-header');") unless @header_red
347
+ @header_red = true
348
+ @builder.text!("makeRed('scenario_#{@scenario_number}');") unless @scenario_red
349
+ @scenario_red = true
350
+ end
351
+ end
315
352
  end
316
- end
317
353
 
318
- def build_step(keyword, step_match, status)
319
- step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
320
- builder.div do |div|
321
- builder.span(keyword, :class => 'keyword')
322
- builder.text!(' ')
323
- builder.span(:class => 'step val') do |name|
324
- name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
354
+ def get_step_count(features)
355
+ count = 0
356
+ features = features.instance_variable_get("@features")
357
+ features.each do |feature|
358
+ #get background steps
359
+ if feature.instance_variable_get("@background")
360
+ background = feature.instance_variable_get("@background").instance_variable_get("@steps").instance_variable_get("@steps")
361
+ count += background.size
362
+ end
363
+ #get scenarios
364
+ feature.instance_variable_get("@feature_elements").each do |scenario|
365
+ #get steps
366
+ steps = scenario.instance_variable_get("@steps").instance_variable_get("@steps")
367
+ count += steps.size
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
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
325
386
  end
387
+ return count
326
388
  end
327
- end
328
389
 
329
- def build_cell(cell_type, value, attributes)
330
- builder.__send__(cell_type, value, attributes)
331
- end
332
-
333
- def inline_css
334
- builder.style(:type => 'text/css') do
335
- builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
390
+ def build_step(keyword, step_match, status)
391
+ step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
392
+ @builder.div do |div|
393
+ @builder.span(keyword, :class => 'keyword')
394
+ @builder.text!(' ')
395
+ @builder.span(:class => 'step val') do |name|
396
+ name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
397
+ end
398
+ end
399
+ end
400
+
401
+ def build_cell(cell_type, value, attributes)
402
+ @builder.__send__(cell_type, attributes) do
403
+ @builder.div do
404
+ @builder.span(value,:class => 'step param')
405
+ end
406
+ end
407
+ end
408
+
409
+ def inline_css
410
+ @builder.style(:type => 'text/css') do
411
+ @builder << File.read(File.dirname(__FILE__) + '/cucumber.css')
412
+ end
413
+ end
414
+
415
+ def inline_js
416
+ @builder.script(:type => 'text/javascript') do
417
+ @builder << inline_js_content
418
+ end
419
+ end
420
+
421
+ def inline_js_content
422
+ <<-EOF
423
+ function moveProgressBar(percentDone) {
424
+ document.getElementById("cucumber-header").style.width = percentDone +"%";
425
+ }
426
+ function makeRed(element_id) {
427
+ document.getElementById(element_id).style.background = '#C40D0D';
428
+ document.getElementById(element_id).style.color = '#FFFFFF';
429
+ }
430
+
431
+ function makeYellow(element_id) {
432
+ document.getElementById(element_id).style.background = '#FAF834';
433
+ document.getElementById(element_id).style.color = '#000000';
434
+ }
435
+ EOF
436
+ end
437
+
438
+ def move_progress
439
+ @builder << " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
440
+ end
441
+
442
+ def percent_done
443
+ result = 100.0
444
+ if @step_count != 0
445
+ result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
446
+ end
447
+ result
448
+ end
449
+
450
+ def format_exception(exception)
451
+ (["#{exception.message}"] + exception.backtrace).join("\n")
452
+ end
453
+
454
+ def backtrace_line(line)
455
+ line.gsub(/^([^:]*\.(?:rb|feature|haml)):(\d*)/) do
456
+ if ENV['TM_PROJECT_DIRECTORY']
457
+ "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
458
+ else
459
+ line
460
+ end
461
+ end
462
+ end
463
+
464
+ def print_stats(features)
465
+ @builder << "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
466
+ @builder << "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
467
+ end
468
+
469
+ def print_stat_string(features)
470
+ string = String.new
471
+ string << dump_count(@step_mother.scenarios.length, "scenario")
472
+ scenario_count = print_status_counts{|status| @step_mother.scenarios(status)}
473
+ string << scenario_count if scenario_count
474
+ string << "<br />"
475
+ string << dump_count(@step_mother.steps.length, "step")
476
+ step_count = print_status_counts{|status| @step_mother.steps(status)}
477
+ string << step_count if step_count
478
+ end
479
+
480
+ def print_status_counts
481
+ counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
482
+ elements = yield status
483
+ elements.any? ? "#{elements.length} #{status.to_s}" : nil
484
+ end.compact
485
+ return " (#{counts.join(', ')})" if counts.any?
486
+ end
487
+
488
+ def dump_count(count, what, state=nil)
489
+ [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ")
490
+ end
491
+
492
+ def create_builder(io)
493
+ OrderedXmlMarkup.new(:target => io, :indent => 0)
336
494
  end
337
- end
338
-
339
- def format_exception(exception)
340
- h((["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n"))
341
- end
342
-
343
- def builder
344
- @current_builder
345
- end
346
-
347
- def buffer(label)
348
- result = @buffer[label]
349
- @buffer[label] = ''
350
- result
351
- end
352
-
353
- def start_buffering(label)
354
- @buffer[label] ||= ''
355
- @parent_builder ||= {}
356
- @parent_builder[label] = @current_builder
357
- @current_builder = create_builder(@buffer[label])
358
- end
359
-
360
- def stop_buffering(label)
361
- @current_builder = @parent_builder[label]
362
- end
363
-
364
- def create_builder(io)
365
- OrderedXmlMarkup.new(:target => io, :indent => 0)
366
- end
367
495
  end
368
496
  end
369
497
  end
498
+
499
+
500
+ class SnippetExtractor #:nodoc:
501
+ class NullConverter; def convert(code, pre); code; end; end #:nodoc:
502
+ begin; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end
503
+
504
+ def snippet(error)
505
+ raw_code, line = snippet_for(error[0])
506
+ highlighted = @@converter.convert(raw_code, false)
507
+ highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
508
+ post_process(highlighted, line)
509
+ end
510
+
511
+ def snippet_for(error_line)
512
+ if error_line =~ /(.*):(\d+)/
513
+ file = $1
514
+ line = $2.to_i
515
+ [lines_around(file, line), line]
516
+ else
517
+ return snippet_for()
518
+ ["# Couldn't get snippet for #{error_line}", 1]
519
+ end
520
+ end
521
+
522
+ def lines_around(file, line)
523
+ if File.file?(file)
524
+ lines = File.open(file).read.split("\n")
525
+ min = [0, line-3].max
526
+ max = [line+1, lines.length-1].min
527
+ selected_lines = []
528
+ selected_lines.join("\n")
529
+ lines[min..max].join("\n")
530
+ else
531
+ "# Couldn't get snippet for #{file}"
532
+ end
533
+ end
534
+
535
+ def post_process(highlighted, offending_line)
536
+ new_lines = []
537
+ highlighted.split("\n").each_with_index do |line, i|
538
+ new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
539
+ new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
540
+ new_lines << new_line
541
+ end
542
+ new_lines.join("\n")
543
+ end
544
+
545
+ end