turnip 0.3.1 → 1.0.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 (49) hide show
  1. data/README.md +128 -166
  2. data/examples/autoload_steps.feature +3 -2
  3. data/examples/errors.feature +8 -0
  4. data/examples/step_calling.feature +2 -0
  5. data/examples/steps/alignment_steps.rb +23 -0
  6. data/examples/{autoload_steps.rb → steps/autoload_steps.rb} +0 -0
  7. data/examples/steps/backtick_steps.rb +10 -0
  8. data/examples/steps/dragon_steps.rb +41 -0
  9. data/examples/{knight_steps.rb → steps/knight_steps.rb} +3 -1
  10. data/examples/{more_steps.rb → steps/more_steps.rb} +0 -0
  11. data/examples/{step_calling_steps.rb → steps/step_calling_steps.rb} +0 -0
  12. data/examples/{steps.rb → steps/steps.rb} +25 -1
  13. data/examples/steps_for.feature +2 -2
  14. data/examples/steps_with_variations.feature +17 -0
  15. data/lib/turnip.rb +19 -34
  16. data/lib/turnip/builder.rb +26 -24
  17. data/lib/turnip/define.rb +9 -0
  18. data/lib/turnip/dsl.rb +11 -17
  19. data/lib/turnip/execute.rb +15 -0
  20. data/lib/turnip/rspec.rb +80 -0
  21. data/lib/turnip/step_definition.rb +7 -32
  22. data/lib/turnip/version.rb +1 -1
  23. data/spec/builder_spec.rb +1 -40
  24. data/spec/define_and_execute.rb +38 -0
  25. data/spec/dsl_spec.rb +36 -19
  26. data/spec/integration_spec.rb +5 -1
  27. data/spec/spec_helper.rb +1 -3
  28. data/spec/step_definition_spec.rb +37 -51
  29. metadata +25 -55
  30. data/examples/alignment_steps.rb +0 -7
  31. data/examples/backtick_steps.rb +0 -4
  32. data/examples/dragon_steps.rb +0 -17
  33. data/examples/evil_steps.rb +0 -7
  34. data/examples/neutral_steps.rb +0 -7
  35. data/examples/red_dragon_steps.rb +0 -18
  36. data/lib/turnip/config.rb +0 -18
  37. data/lib/turnip/feature_file.rb +0 -20
  38. data/lib/turnip/loader.rb +0 -16
  39. data/lib/turnip/runner_dsl.rb +0 -9
  40. data/lib/turnip/scenario_context.rb +0 -41
  41. data/lib/turnip/scenario_runner.rb +0 -35
  42. data/lib/turnip/step_loader.rb +0 -27
  43. data/lib/turnip/step_module.rb +0 -89
  44. data/spec/feature_file_spec.rb +0 -18
  45. data/spec/runner_dsl_spec.rb +0 -23
  46. data/spec/scenario_context_spec.rb +0 -51
  47. data/spec/scenario_runner_spec.rb +0 -79
  48. data/spec/step_loader_spec.rb +0 -29
  49. data/spec/step_module_spec.rb +0 -106
@@ -0,0 +1,10 @@
1
+ step "I run :cmd" do |cmd|
2
+ step('I attack it') if cmd == 'killall monsters'
3
+ end
4
+
5
+ placeholder :cmd do
6
+ match /`([^`]*)`/ do |cmd|
7
+ cmd
8
+ end
9
+ end
10
+
@@ -0,0 +1,41 @@
1
+ require_relative "knight_steps"
2
+
3
+ module DragonSteps
4
+ include KnightSteps
5
+
6
+ attr_accessor :dragon
7
+
8
+ def dragon_attack
9
+ dragon * 10
10
+ end
11
+
12
+ step "there is a dragon" do
13
+ self.dragon = 1
14
+ end
15
+
16
+ step "the dragon attacks the knight" do
17
+ knight.attacked_for(dragon_attack)
18
+ end
19
+ end
20
+
21
+ module RedDragonSteps
22
+ include DragonSteps
23
+
24
+ attr_accessor :red_dragon
25
+
26
+ def dragon_attack
27
+ attack = super
28
+ if red_dragon
29
+ attack + 15
30
+ else
31
+ attack
32
+ end
33
+ end
34
+
35
+ step "the dragon breathes fire" do
36
+ self.red_dragon = 1
37
+ end
38
+ end
39
+
40
+ RSpec.configure { |c| c.include DragonSteps, :dragon => true }
41
+ RSpec.configure { |c| c.include RedDragonSteps, :red_dragon => true }
@@ -1,4 +1,4 @@
1
- steps_for :knight do
1
+ module KnightSteps
2
2
  attr_accessor :knight
3
3
 
4
4
  class Knight
@@ -27,3 +27,5 @@ steps_for :knight do
27
27
  knight.should_not be_alive
28
28
  end
29
29
  end
30
+
31
+ RSpec.configure { |c| c.include KnightSteps, :knight => true }
@@ -17,7 +17,7 @@ end
17
17
  step "this is ambiguous" do
18
18
  end
19
19
 
20
- step "this is ambiguous" do
20
+ step "this is :ambiguous" do
21
21
  end
22
22
 
23
23
  step "there is a monster called :name" do |name|
@@ -79,6 +79,30 @@ step "the song should have :count lines" do |count|
79
79
  @song.to_s.split("\n").length.should eq(count)
80
80
  end
81
81
 
82
+ step "it should be strong/tough" do
83
+ @monster.should >= 2
84
+ end
85
+
86
+ step "it should be (a) badass" do
87
+ @monster.should >= 2
88
+ end
89
+
90
+ step "it should be (a) badass" do
91
+ @monster.should >= 2
92
+ end
93
+
94
+ step "it should be terrible(st)" do
95
+ @monster.should >= 2
96
+ end
97
+
98
+ step "it (should) have/has :count (terrifying) hitpoint(s)" do |count|
99
+ @monster.should == count
100
+ end
101
+
102
+ step "raise error" do
103
+ raise "foobar"
104
+ end
105
+
82
106
  placeholder :count do
83
107
  match /\d+/ do |count|
84
108
  count.to_i
@@ -1,10 +1,10 @@
1
1
  Feature: Steps for a feature
2
2
  @evil
3
- Scenario:
3
+ Scenario: Evil
4
4
  Given the monster has an alignment
5
5
  Then that alignment should be "Evil"
6
6
 
7
7
  @neutral
8
- Scenario:
8
+ Scenario: Neutral
9
9
  Given the monster has an alignment
10
10
  Then that alignment should be "Neutral"
@@ -0,0 +1,17 @@
1
+ Feature: steps with variations, such as alternative words or optional words
2
+ Scenario: alternative words
3
+ Given there is a strong monster
4
+ Then it should be strong
5
+ And it should be tough
6
+ Scenario: optional words
7
+ Given there is a strong monster
8
+ Then it should be badass
9
+ And it should be a badass
10
+ Scenario: optional parts of words
11
+ Given there is a strong monster
12
+ Then it should be terrible
13
+ And it should be terriblest
14
+ Scenario: putting it all together
15
+ Given there is a strong monster
16
+ Then it should have 2 terrifying hitpoints
17
+ And it has 2 hitpoint
@@ -1,48 +1,33 @@
1
- require "gherkin"
2
- require "gherkin/formatter/tag_count_formatter"
3
-
4
1
  require "turnip/version"
5
2
  require "turnip/dsl"
6
-
7
- require 'rspec'
3
+ require "turnip/execute"
4
+ require "turnip/define"
5
+ require "turnip/builder"
6
+ require "turnip/step_definition"
7
+ require "turnip/placeholder"
8
+ require "turnip/table"
8
9
 
9
10
  module Turnip
10
- autoload :Config, 'turnip/config'
11
- autoload :FeatureFile, 'turnip/feature_file'
12
- autoload :Loader, 'turnip/loader'
13
- autoload :Builder, 'turnip/builder'
14
- autoload :StepDefinition, 'turnip/step_definition'
15
- autoload :Placeholder, 'turnip/placeholder'
16
- autoload :Table, 'turnip/table'
17
- autoload :StepLoader, 'turnip/step_loader'
18
- autoload :StepModule, 'turnip/step_module'
19
- autoload :ScenarioRunner, 'turnip/scenario_runner'
20
- autoload :RunnerDSL, 'turnip/runner_dsl'
21
- autoload :ScenarioContext, 'turnip/scenario_context'
11
+ class Pending < StandardError; end
12
+ class Ambiguous < StandardError; end
13
+
14
+ ##
15
+ #
16
+ # The global step module, adding steps here will make them available in all
17
+ # your tests.
18
+ #
19
+ module Steps
20
+ end
22
21
 
23
22
  class << self
24
23
  attr_accessor :type
25
-
26
- def run(feature_file)
27
- Turnip::Builder.build(feature_file).features.each do |feature|
28
- describe feature.name, feature.metadata_hash do
29
- feature.scenarios.each do |scenario|
30
- it scenario.name, scenario.metadata_hash do
31
- Turnip::ScenarioRunner.new(self).load(Turnip::ScenarioContext.new(feature, scenario)).run
32
- end
33
- end
34
- end
35
- end
36
- end
37
24
  end
38
25
  end
39
26
 
40
27
  Turnip.type = :turnip
41
28
 
42
- RSpec::Core::Configuration.send(:include, Turnip::Loader)
43
-
44
- RSpec.configure do |config|
45
- config.pattern << ",**/*.feature"
46
- end
29
+ Module.send(:include, Turnip::Define)
47
30
 
48
31
  self.extend Turnip::DSL
32
+
33
+ require "turnip/rspec"
@@ -1,13 +1,11 @@
1
+ require "gherkin"
2
+
1
3
  module Turnip
2
4
  class Builder
3
5
  module Tags
4
6
  def tags
5
7
  @raw.tags.map { |tag| tag.name.sub(/^@/, '') }
6
8
  end
7
-
8
- def active_tags
9
- tags.map(&:to_sym)
10
- end
11
9
 
12
10
  def tags_hash
13
11
  Hash[tags.map { |t| [t.to_sym, true] }]
@@ -36,13 +34,9 @@ module Turnip
36
34
  @scenarios = []
37
35
  @backgrounds = []
38
36
  end
39
-
40
- # Feature's active_tags automatically prepends the :global tag
41
- # as well as its feature_tag if defined
42
- def active_tags
43
- active_tags = [:global]
44
- active_tags << feature_tag.to_sym if feature_tag
45
- active_tags + super
37
+
38
+ def line
39
+ @raw.line
46
40
  end
47
41
 
48
42
  def metadata_hash
@@ -88,30 +82,36 @@ module Turnip
88
82
  Scenario.new(@raw).tap do |scenario|
89
83
  scenario.steps = steps.map do |step|
90
84
  new_description = step.description.gsub(/<([^>]*)>/) { |_| Hash[headers.zip(row)][$1] }
91
- Step.new(new_description, step.extra_arg)
85
+ Step.new(new_description, step.extra_args, step.line)
92
86
  end
93
87
  end
94
88
  end
95
89
  end
96
90
  end
97
91
 
98
- class Step < Struct.new(:description, :extra_arg)
92
+ class Step < Struct.new(:description, :extra_args, :line)
93
+ # 1.9.2 support hack
94
+ def split(*args)
95
+ self.to_s.split(*args)
96
+ end
97
+
98
+ def to_s
99
+ description
100
+ end
99
101
  end
100
102
 
101
103
  attr_reader :features
102
104
 
103
105
  class << self
104
106
  def build(feature_file)
105
- Turnip::Builder.new(feature_file).tap do |builder|
106
- formatter = Gherkin::Formatter::TagCountFormatter.new(builder, {})
107
- parser = Gherkin::Parser::Parser.new(formatter, true, "root", false)
108
- parser.parse(feature_file.content, nil, 0)
107
+ Turnip::Builder.new.tap do |builder|
108
+ parser = Gherkin::Parser::Parser.new(builder, true)
109
+ parser.parse(File.read(feature_file), nil, 0)
109
110
  end
110
111
  end
111
112
  end
112
113
 
113
- def initialize(feature_file)
114
- @feature_file = feature_file
114
+ def initialize
115
115
  @features = []
116
116
  end
117
117
 
@@ -122,8 +122,6 @@ module Turnip
122
122
 
123
123
  def feature(feature)
124
124
  @current_feature = Feature.new(feature)
125
- # Automatically add a tag based on the name of the feature to the Feature if configured to
126
- @current_feature.feature_tag = @feature_file.feature_name if Turnip::Config.autotag_features
127
125
  @features << @current_feature
128
126
  end
129
127
 
@@ -141,12 +139,16 @@ module Turnip
141
139
  end
142
140
 
143
141
  def step(step)
142
+ extra_args = []
144
143
  if step.doc_string
145
- extra_arg = step.doc_string.value
144
+ extra_args.push step.doc_string.value
146
145
  elsif step.rows
147
- extra_arg = Turnip::Table.new(step.rows.map { |row| row.cells(&:value) })
146
+ extra_args.push Turnip::Table.new(step.rows.map { |row| row.cells(&:value) })
148
147
  end
149
- @current_step_context.steps << Step.new(step.name, extra_arg)
148
+ @current_step_context.steps << Step.new(step.name, extra_args, step.line)
149
+ end
150
+
151
+ def uri(*)
150
152
  end
151
153
 
152
154
  def eof
@@ -0,0 +1,9 @@
1
+ module Turnip
2
+ module Define
3
+ def step(expression, &block)
4
+ step = Turnip::StepDefinition.new(expression, &block)
5
+ send(:define_method, "match: #{expression}") { |description| step.match(description) }
6
+ send(:define_method, expression, &block)
7
+ end
8
+ end
9
+ end
@@ -5,26 +5,20 @@ module Turnip
5
5
  end
6
6
 
7
7
  def step(description, &block)
8
- global_step_module_entry.step_module.steps << Turnip::StepDefinition.new(description, &block)
8
+ Turnip::Steps.step(description, &block)
9
9
  end
10
10
 
11
11
  def steps_for(tag, &block)
12
- Turnip::StepModule.steps_for(tag, &block)
13
- end
14
-
15
- private
16
-
17
- def global_step_module_entry
18
- @global_step_module_entry ||= begin
19
- anon = Module.new do
20
- def self.steps
21
- @steps ||= []
22
- end
23
- end
24
- entry = Turnip::StepModule::Entry.new([:global], anon, [])
25
- Turnip::StepModule.module_registry[:global] << entry
26
- entry
27
- end
12
+ if tag.to_s == "global"
13
+ warn "[Turnip] using steps_for(:global) is deprecated, add steps to Turnip::Steps instead"
14
+ Turnip::Steps.module_eval(&block)
15
+ else
16
+ Module.new do
17
+ singleton_class.send(:define_method, :tag) { tag }
18
+ module_eval(&block)
19
+ ::RSpec.configure { |c| c.include self, tag => true }
20
+ end
21
+ end
28
22
  end
29
23
  end
30
24
  end
@@ -0,0 +1,15 @@
1
+ module Turnip
2
+ module Execute
3
+ def step(description, *extra_args)
4
+ extra_args.concat(description.extra_args) if description.respond_to?(:extra_args)
5
+
6
+ matches = methods.map do |method|
7
+ next unless method.to_s.start_with?("match: ")
8
+ send(method.to_s, description.to_s)
9
+ end.compact
10
+ raise Turnip::Pending, description if matches.length == 0
11
+ raise Turnip::Ambiguous, description if matches.length > 1
12
+ send(matches.first.expression, *(matches.first.params + extra_args))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,80 @@
1
+ require "turnip"
2
+ require "rspec"
3
+
4
+ module Turnip
5
+ module RSpec
6
+
7
+ ##
8
+ #
9
+ # This module hooks Turnip into RSpec by duck punching the load Kernel
10
+ # method. If the file is a feature file, we run Turnip instead!
11
+ #
12
+ module Loader
13
+ def load(*a, &b)
14
+ if a.first.end_with?('.feature')
15
+ begin
16
+ require 'spec_helper'
17
+ rescue LoadError
18
+ end
19
+ Turnip::RSpec.run(a.first)
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+
26
+ ##
27
+ #
28
+ # This module provides an improved method to run steps inside RSpec, adding
29
+ # proper support for pending steps, as well as nicer backtraces.
30
+ #
31
+ module Execute
32
+ include Turnip::Execute
33
+
34
+ def run_step(feature_file, step)
35
+ begin
36
+ step(step)
37
+ rescue Turnip::Pending
38
+ pending("No such step: '#{step}'")
39
+ rescue StandardError => e
40
+ e.backtrace.push "#{feature_file}:#{step.line}:in `#{step.description}'"
41
+ raise e
42
+ end
43
+ end
44
+ end
45
+
46
+ class << self
47
+ def run(feature_file)
48
+ Turnip::Builder.build(feature_file).features.each do |feature|
49
+ describe feature.name, feature.metadata_hash do
50
+ before do
51
+ # This is kind of a hack, but it will make RSpec throw way nicer exceptions
52
+ example.metadata[:file_path] = feature_file
53
+
54
+ feature.backgrounds.map(&:steps).flatten.each do |step|
55
+ run_step(feature_file, step)
56
+ end
57
+ end
58
+ feature.scenarios.each do |scenario|
59
+ describe scenario.name, scenario.metadata_hash do
60
+ it scenario.steps.map(&:description).join(' -> ') do
61
+ scenario.steps.each do |step|
62
+ run_step(feature_file, step)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ ::RSpec::Core::Configuration.send(:include, Turnip::RSpec::Loader)
75
+
76
+ ::RSpec.configure do |config|
77
+ config.include Turnip::RSpec::Execute
78
+ config.include Turnip::Steps
79
+ config.pattern << ",**/*.feature"
80
+ end