cucumber 0.7.3 → 0.8.0

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