cucumber 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.txt +20 -1
  2. data/Rakefile +4 -4
  3. data/VERSION.yml +2 -2
  4. data/cucumber.gemspec +35 -23
  5. data/examples/json/features/background.feature +7 -0
  6. data/examples/json/features/one_passing_one_failing.feature +11 -0
  7. data/examples/json/features/pystring.feature +8 -0
  8. data/examples/json/features/step_definitions/steps.rb +27 -0
  9. data/examples/json/features/tables.feature +13 -0
  10. data/examples/json/tmp/out.json +1 -0
  11. data/examples/self_test/features/step_definitions/sample_steps.rb +1 -0
  12. data/examples/tickets/features/around_timeout.feature +6 -0
  13. data/examples/tickets/features/step_definitons/around_timeout_steps.rb +9 -0
  14. data/examples/{javascript → v8}/Rakefile +3 -1
  15. data/examples/{javascript → v8}/features/fibonacci.feature +4 -7
  16. data/examples/{javascript → v8}/features/step_definitions/fib_steps.js +7 -3
  17. data/examples/{javascript → v8}/features/support/env.js +3 -0
  18. data/examples/{javascript/features → v8}/lib/fibonacci.js +0 -0
  19. data/features/announce.feature +1 -1
  20. data/features/cucumber_cli_diff_disabled.feature +1 -24
  21. data/features/custom_formatter.feature +9 -3
  22. data/features/json_formatter.feature +281 -0
  23. data/features/step_definitions/cucumber_steps.rb +6 -1
  24. data/gem_tasks/rspec.rake +1 -1
  25. data/lib/cucumber/ast/feature.rb +1 -1
  26. data/lib/cucumber/cli/configuration.rb +8 -12
  27. data/lib/cucumber/cli/main.rb +0 -8
  28. data/lib/cucumber/cli/options.rb +22 -21
  29. data/lib/cucumber/formatter/json.rb +154 -0
  30. data/lib/cucumber/formatter/json_pretty.rb +14 -0
  31. data/lib/cucumber/js_support/js_dsl.js +14 -26
  32. data/lib/cucumber/js_support/js_language.rb +31 -16
  33. data/lib/cucumber/rb_support/rb_language.rb +22 -3
  34. data/spec/cucumber/ast/scenario_spec.rb +1 -1
  35. data/spec/cucumber/cli/configuration_spec.rb +8 -16
  36. data/spec/cucumber/cli/main_spec.rb +5 -5
  37. data/spec/cucumber/formatter/html_spec.rb +1 -1
  38. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +4 -4
  39. data/spec/cucumber/step_mother_spec.rb +1 -1
  40. data/spec/cucumber/world/pending_spec.rb +1 -1
  41. data/spec/spec_helper.rb +1 -1
  42. metadata +65 -21
  43. data/lib/cucumber/rspec/diffing.rb +0 -17
@@ -0,0 +1,154 @@
1
+ require "json"
2
+ require "cucumber/formatter/io"
3
+
4
+ module Cucumber
5
+ module Formatter
6
+ # The formatter used for <tt>--format json</tt>
7
+ class Json
8
+ class Error < StandardError
9
+ end
10
+
11
+ include Io
12
+
13
+ def initialize(step_mother, io, options)
14
+ @io = ensure_io(io, "json")
15
+ @options = options
16
+ end
17
+
18
+ def before_features(features)
19
+ @json = {:features => []}
20
+ end
21
+
22
+ def before_feature(feature)
23
+ @current_object = {:file => feature.file, :name => feature.name}
24
+ @json[:features] << @current_object
25
+ end
26
+
27
+ def before_tags(tags)
28
+ @current_object[:tags] = tags.tag_names
29
+ end
30
+
31
+ def before_background(background)
32
+ background = {}
33
+ @current_object[:background] = background
34
+ @current_object = background
35
+ end
36
+
37
+ def after_background(background)
38
+ @current_object = last_feature
39
+ end
40
+
41
+ def before_feature_element(feature_element)
42
+ elements = @current_object[:elements] ||= []
43
+
44
+ # change current object to the feature_element
45
+ @current_object = {}
46
+ elements << @current_object
47
+ end
48
+
49
+ def scenario_name(keyword, name, file_colon_line, source_indent)
50
+ @current_object[:keyword] = keyword
51
+ @current_object[:name] = name
52
+ @current_object[:file_colon_line] = file_colon_line
53
+ end
54
+
55
+ def before_steps(steps)
56
+ @current_object[:steps] = []
57
+ end
58
+
59
+ def before_step(step)
60
+ @current_step = {}
61
+ @current_object[:steps] << @current_step
62
+ end
63
+
64
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
65
+ if exception
66
+ @current_step[:exception] = exception_hash_for(exception)
67
+ end
68
+ end
69
+
70
+ def step_name(keyword, step_match, status, source_indent, background)
71
+ @current_step[:status] = status
72
+ @current_step[:name] = "#{keyword}#{step_match.name || step_match.format_args}" # ?
73
+ @current_step[:file_colon_line] = step_match.file_colon_line
74
+ end
75
+
76
+ def after_step(step)
77
+ @current_step = nil
78
+ end
79
+
80
+ def before_examples(examples)
81
+ @current_object[:examples] = {}
82
+ end
83
+
84
+ def examples_name(keyword, name)
85
+ @current_object[:examples][:name] = "#{keyword} #{name}"
86
+ end
87
+
88
+ def before_outline_table(*args)
89
+ @current_object[:examples][:table] = []
90
+ end
91
+
92
+ def before_table_row(row)
93
+ @current_row = {:cells => []}
94
+
95
+ if @current_object.member? :examples
96
+ @current_object[:examples][:table] << @current_row
97
+ elsif @current_step
98
+ (@current_step[:table] ||= []) << @current_row
99
+ else
100
+ internal_error
101
+ end
102
+ end
103
+
104
+ def table_cell_value(value, status)
105
+ @current_row[:cells] << {:text => value, :status => status}
106
+ end
107
+
108
+ def after_table_row(row)
109
+ if row.exception
110
+ @current_row[:exception] = exception_hash_for(row.exception)
111
+ end
112
+ @current_row = nil
113
+ end
114
+
115
+ def py_string(string)
116
+ @current_step[:py_string] = string
117
+ end
118
+
119
+ def after_feature_element(feature_element)
120
+ # change current object back to the last feature
121
+ @current_object = last_feature
122
+ end
123
+
124
+ def after_features(features)
125
+ @io.write json_string
126
+ @io.flush
127
+ end
128
+
129
+ private
130
+
131
+ def json_string
132
+ @json.to_json
133
+ end
134
+
135
+ def last_feature
136
+ @json[:features].last
137
+ end
138
+
139
+ def exception_hash_for(e)
140
+ {
141
+ :class => e.class.name,
142
+ :message => e.message,
143
+ :backtrace => e.backtrace
144
+ }
145
+ end
146
+
147
+ def internal_error
148
+ raise Error, "you've found a bug in the JSON formatter!"
149
+ end
150
+
151
+ end # Json
152
+ end # Formatter
153
+ end # Cucumber
154
+
@@ -0,0 +1,14 @@
1
+ require "cucumber/formatter/json"
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ # The formatter used for <tt>--format json_pretty</tt>
6
+ class JsonPretty < Json
7
+
8
+ def json_string
9
+ JSON.pretty_generate @json
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -20,8 +20,16 @@ var CucumberJsDsl = {
20
20
  CucumberJsDsl.__registerJsHook('after', tag_expressions_or_func, func);
21
21
  },
22
22
 
23
+ steps: function(step_names){
24
+ jsLanguage.steps(step_names);
25
+ },
26
+
23
27
  Table: function(raw_table){
24
- this.raw = raw_table;
28
+ //TODO: Create a ruby table and send it back for use in js world
29
+ },
30
+
31
+ world: function(files){
32
+ jsLanguage.world(files);
25
33
  },
26
34
 
27
35
  __registerJsHook: function(label, tag_expressions_or_func, func){
@@ -36,34 +44,14 @@ var CucumberJsDsl = {
36
44
  }
37
45
  }
38
46
 
39
- CucumberJsDsl.Table.prototype.hashes = function(){
40
- var rows = this.rows();
41
- var headers = this.headers();
42
- var hashes = [];
43
-
44
- for (var rowIndex in rows){
45
- var hash_row = [];
46
- for (var cellIndex in headers){
47
- hash_row[headers[cellIndex]] = rows[rowIndex][cellIndex];
48
- }
49
- hashes[rowIndex] = hash_row;
50
- }
51
- return hashes;
52
- }
53
-
54
- CucumberJsDsl.Table.prototype.rows = function(){
55
- return this.raw.slice(1);
56
- }
57
-
58
- CucumberJsDsl.Table.prototype.headers = function(){
59
- var raw_cells = this.raw.slice(0);
60
- return raw_cells.shift();
61
- }
62
-
63
47
  var Given = CucumberJsDsl.registerStepDefinition;
64
48
  var When = CucumberJsDsl.registerStepDefinition;
65
49
  var Then = CucumberJsDsl.registerStepDefinition;
66
50
 
67
51
  var Before = CucumberJsDsl.beforeHook;
68
52
  var After = CucumberJsDsl.afterHook;
69
- var Transform = CucumberJsDsl.registerTransform;
53
+ var Transform = CucumberJsDsl.registerTransform;
54
+
55
+ var World = CucumberJsDsl.world;
56
+
57
+ var steps = CucumberJsDsl.steps;
@@ -1,3 +1,4 @@
1
+ gem 'therubyracer', '>=0.7.1'
1
2
  require 'v8'
2
3
 
3
4
  require 'cucumber/js_support/js_snippets'
@@ -7,7 +8,7 @@ module Cucumber
7
8
 
8
9
  def self.argument_safe_string(string)
9
10
  arg_string = string.to_s.gsub(/[']/, '\\\\\'')
10
- "'#{arg_string.gsub("\n", '\n')}'"
11
+ arg_string.gsub("\n", '\n')
11
12
  end
12
13
 
13
14
  class JsWorld
@@ -16,15 +17,7 @@ module Cucumber
16
17
  end
17
18
 
18
19
  def execute(js_function, args=[])
19
- js_args = args.map do |arg|
20
- if arg.is_a?(Ast::Table)
21
- "new CucumberJsDsl.Table(#{arg.raw.inspect})"
22
- else
23
- JsSupport.argument_safe_string(arg)
24
- end
25
- end
26
-
27
- @world.eval("(#{js_function.ToString})(#{js_args.join(',')});")
20
+ js_function.call(*args)
28
21
  end
29
22
 
30
23
  def method_missing(method_name, *args)
@@ -34,7 +27,7 @@ module Cucumber
34
27
 
35
28
  class JsStepDefinition
36
29
  def initialize(js_language, regexp, js_function)
37
- @js_language, @regexp, @js_function = js_language, regexp.ToString, js_function
30
+ @js_language, @regexp, @js_function = js_language, regexp.to_s, js_function
38
31
  end
39
32
 
40
33
  def invoke(args)
@@ -45,7 +38,7 @@ module Cucumber
45
38
  def arguments_from(step_name)
46
39
  matches = eval_js "#{@regexp}.exec('#{step_name}')"
47
40
  if matches
48
- matches[1..-1].map do |match|
41
+ matches.to_a[1..-1].map do |match|
49
42
  JsArg.new(match)
50
43
  end
51
44
  end
@@ -73,17 +66,17 @@ module Cucumber
73
66
 
74
67
  class JsTransform
75
68
  def initialize(js_language, regexp, js_function)
76
- @js_language, @regexp, @js_function = js_language, regexp.ToString, js_function
69
+ @js_language, @regexp, @js_function = js_language, regexp.to_s, js_function
77
70
  end
78
71
 
79
72
  def match(arg)
80
73
  arg = JsSupport.argument_safe_string(arg)
81
- matches = eval_js "#{@regexp}.exec(#{arg});"
82
- matches ? matches[1..-1] : nil
74
+ matches = (eval_js "#{@regexp}.exec('#{arg}');").to_a
75
+ matches.empty? ? nil : matches[1..-1]
83
76
  end
84
77
 
85
78
  def invoke(arg)
86
- @js_language.current_world.execute(@js_function, [arg])
79
+ @js_function.call([arg])
87
80
  end
88
81
  end
89
82
 
@@ -117,10 +110,17 @@ module Cucumber
117
110
  @world.load(js_file)
118
111
  end
119
112
 
113
+ def world(js_files)
114
+ js_files.each do |js_file|
115
+ load_code_file("#{path_to_load_js_from}#{js_file}")
116
+ end
117
+ end
118
+
120
119
  def alias_adverbs(adverbs)
121
120
  end
122
121
 
123
122
  def begin_scenario(scenario)
123
+ @language = scenario.language
124
124
  end
125
125
 
126
126
  def end_scenario
@@ -157,6 +157,21 @@ module Cucumber
157
157
  @world
158
158
  end
159
159
 
160
+ def steps(steps_text)
161
+ @step_mother.invoke_steps(steps_text, @language)
162
+ end
163
+
164
+ private
165
+ def path_to_load_js_from
166
+ paths = @step_mother.options[:paths]
167
+ if paths.empty?
168
+ '' # Using rake
169
+ else
170
+ path = paths[0][/(^.*\/?features)/, 0]
171
+ path ? "#{path}/../" : '../'
172
+ end
173
+ end
174
+
160
175
  end
161
176
  end
162
177
  end
@@ -41,6 +41,26 @@ module Cucumber
41
41
  @step_definitions = []
42
42
  RbDsl.rb_language = self
43
43
  @world_proc = @world_modules = nil
44
+ enable_rspec_expectations_if_available
45
+ end
46
+
47
+ def enable_rspec_expectations_if_available
48
+ begin
49
+ # RSpec >=2.0
50
+ require 'rspec/expectations'
51
+ @rspec_matchers = ::RSpec::Matchers
52
+ rescue LoadError => try_rspec_1_2_4_or_higher
53
+ begin
54
+ require 'spec/expectations'
55
+ require 'spec/runner/differs/default'
56
+ require 'ostruct'
57
+ options = OpenStruct.new(:diff_format => :unified, :context_lines => 3)
58
+ Spec::Expectations.differ = Spec::Expectations::Differs::Default.new(options)
59
+ @rspec_matchers = ::Spec::Matchers
60
+ rescue LoadError => give_up
61
+ @rspec_matchers = Module.new{}
62
+ end
63
+ end
44
64
  end
45
65
 
46
66
  # Gets called for each file under features (or whatever is overridden
@@ -132,7 +152,7 @@ module Cucumber
132
152
  private
133
153
 
134
154
  PARAM_PATTERN = /"([^"]*)"/
135
- ESCAPED_PARAM_PATTERN = '"([^\\"]*)"'
155
+ ESCAPED_PARAM_PATTERN = '"([^"]*)"'
136
156
 
137
157
  def create_world
138
158
  if(@world_proc)
@@ -145,8 +165,7 @@ module Cucumber
145
165
 
146
166
  def extend_world
147
167
  @current_world.extend(RbWorld)
148
- @current_world.extend(::Spec::Matchers) if defined?(::Spec::Matchers) # RSpec 1.x
149
- @current_world.extend(::Rspec::Matchers) if defined?(::Rspec::Matchers) # RSpec 2.x
168
+ @current_world.extend(@rspec_matchers)
150
169
  (@world_modules || []).each do |mod|
151
170
  @current_world.extend(mod)
152
171
  end
@@ -33,7 +33,7 @@ module Cucumber
33
33
  Step.new(8, "Given", "y is 5")
34
34
  ]
35
35
  )
36
- scenario.feature = mock('feature', :null_object => true)
36
+ scenario.feature = mock('feature').as_null_object
37
37
  @visitor.visit_feature_element(scenario)
38
38
 
39
39
  $y.should == nil
@@ -54,7 +54,7 @@ module Cli
54
54
  end
55
55
 
56
56
  it "should require files in vendor/{plugins,gems}/*/cucumber/*.rb" do
57
- given_the_following_files("/vendor/gems/gem_a/cucumber/bar.rb",
57
+ given_the_following_files("/vendor/gems/gem_a/cucumber/bar.rb",
58
58
  "/vendor/plugins/plugin_a/cucumber/foo.rb")
59
59
 
60
60
  config.parse!(%w{--require /features})
@@ -142,7 +142,7 @@ module Cli
142
142
 
143
143
  it "allows --strict to be set by a profile" do
144
144
  given_cucumber_yml_defined_as({'bongo' => '--strict'})
145
-
145
+
146
146
  config.parse!(%w{--profile bongo})
147
147
  config.options[:strict].should be_true
148
148
  end
@@ -347,20 +347,6 @@ END_OF_MESSAGE
347
347
  end
348
348
  end
349
349
 
350
- describe "diff output" do
351
-
352
- it "is enabled by default" do
353
- config.diff_enabled?.should be_true
354
- end
355
-
356
- it "is disabled when the --no-diff option is supplied" do
357
- config.parse!(%w{--no-diff})
358
-
359
- config.diff_enabled?.should be_false
360
- end
361
-
362
- end
363
-
364
350
  it "should accept multiple --name options" do
365
351
  config.parse!(['--name', "User logs in", '--name', "User signs up"])
366
352
 
@@ -375,6 +361,12 @@ END_OF_MESSAGE
375
361
  config.options[:name_regexps].should include(/User signs up/)
376
362
  end
377
363
 
364
+ it "should preserve the order of the feature files" do
365
+ config.parse!(%w{b.feature c.feature a.feature})
366
+
367
+ config.feature_files.should == ["b.feature", "c.feature", "a.feature"]
368
+ end
369
+
378
370
  it "should search for all features in the specified directory" do
379
371
  File.stub!(:directory?).and_return(true)
380
372
  Dir.should_receive(:[]).with("feature_directory/**/*.feature").
@@ -41,7 +41,7 @@ module Cucumber
41
41
  Object.stub!(:const_defined?).and_return(true)
42
42
  mock_module.stub!(:const_defined?).and_return(true)
43
43
 
44
- f = stub('formatter', :null_object => true)
44
+ f = stub('formatter').as_null_object
45
45
 
46
46
  Object.should_receive(:const_get).with('ZooModule').and_return(mock_module)
47
47
  mock_module.should_receive(:const_get).with('MonkeyFormatterClass').and_return(mock('formatter class', :new => f))
@@ -55,8 +55,8 @@ module Cucumber
55
55
  describe "setup step sequence" do
56
56
 
57
57
  it "should load files and execute hooks in order" do
58
- Configuration.stub!(:new).and_return(configuration = mock('configuration', :null_object => true))
59
- step_mother = mock('step mother', :null_object => true)
58
+ Configuration.stub!(:new).and_return(configuration = mock('configuration').as_null_object)
59
+ step_mother = mock('step mother').as_null_object
60
60
  configuration.stub!(:drb?).and_return false
61
61
  cli = Main.new(%w{--verbose example.feature}, @out)
62
62
  cli.stub!(:require)
@@ -96,13 +96,13 @@ module Cucumber
96
96
 
97
97
  context "--drb" do
98
98
  before(:each) do
99
- @configuration = mock('Configuration', :drb? => true, :null_object => true)
99
+ @configuration = mock('Configuration', :drb? => true).as_null_object
100
100
  Configuration.stub!(:new).and_return(@configuration)
101
101
 
102
102
  @args = ['features']
103
103
 
104
104
  @cli = Main.new(@args, @out, @err)
105
- @step_mother = mock('StepMother', :null_object => true)
105
+ @step_mother = mock('StepMother').as_null_object
106
106
  end
107
107
 
108
108
  it "delegates the execution to the DRB client passing the args and streams" do