cucumber 0.4.4 → 0.4.5.rc1

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.
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