spinach 0.0.6 → 0.1.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 (41) hide show
  1. data/Gemfile +1 -0
  2. data/Readme.md +3 -0
  3. data/bin/spinach +3 -1
  4. data/features/steps/error_reporting.rb +4 -8
  5. data/features/steps/exit_status.rb +0 -1
  6. data/features/steps/feature_name_guessing.rb +1 -2
  7. data/features/support/spinach_runner.rb +0 -2
  8. data/lib/spinach/capybara.rb +8 -8
  9. data/lib/spinach/cli.rb +30 -18
  10. data/lib/spinach/config.rb +29 -10
  11. data/lib/spinach/dsl.rb +45 -21
  12. data/lib/spinach/exceptions.rb +21 -8
  13. data/lib/spinach/feature.rb +8 -4
  14. data/lib/spinach/parser.rb +13 -7
  15. data/lib/spinach/reporter/stdout.rb +251 -53
  16. data/lib/spinach/reporter.rb +43 -26
  17. data/lib/spinach/runner/feature.rb +39 -26
  18. data/lib/spinach/runner/scenario.rb +43 -28
  19. data/lib/spinach/runner.rb +40 -20
  20. data/lib/spinach/support.rb +8 -7
  21. data/lib/spinach/version.rb +2 -1
  22. data/lib/spinach.rb +17 -9
  23. data/spinach.gemspec +2 -1
  24. data/test/spinach/capybara_test.rb +32 -3
  25. data/test/spinach/cli_test.rb +32 -11
  26. data/test/spinach/config_test.rb +9 -6
  27. data/test/spinach/dsl_test.rb +8 -1
  28. data/test/spinach/feature_test.rb +14 -11
  29. data/test/spinach/parser_test.rb +33 -20
  30. data/test/spinach/reporter/stdout_test.rb +364 -103
  31. data/test/spinach/reporter_test.rb +145 -20
  32. data/test/spinach/runner/feature_test.rb +26 -51
  33. data/test/spinach/runner/scenario_test.rb +64 -35
  34. data/test/spinach/runner_test.rb +41 -32
  35. data/test/spinach/support_test.rb +2 -10
  36. data/test/spinach_test.rb +14 -14
  37. data/test/test_helper.rb +13 -5
  38. metadata +36 -28
  39. data/examples/steps/user_logs_in.rb +0 -23
  40. data/examples/user_logs_in.feature +0 -6
  41. data/examples/user_logs_in.rb +0 -23
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ gemspec
6
6
  group :test do
7
7
  gem 'guard'
8
8
  gem 'guard-minitest'
9
+ gem 'pry', git: 'git://github.com/pry/pry.git'
9
10
  end
10
11
 
11
12
  group :darwin do
data/Readme.md CHANGED
@@ -10,3 +10,6 @@ Spinach is a BDD framework on top of gherkin
10
10
  ![](http://www.80stees.com/images/products/Popeye_the_Sailor_Man_I_Popeye_Spinach-T-link.jpg)
11
11
 
12
12
  *Popeye the Sailor*
13
+
14
+ # Build status
15
+ [![Build Status](https://secure.travis-ci.org/codegram/spinach.png)](http://travis-ci.org/codegram/spinach)
data/bin/spinach CHANGED
@@ -6,4 +6,6 @@ rescue LoadError
6
6
  require_relative '../lib/spinach'
7
7
  end
8
8
 
9
- exit Spinach::Cli.new(ARGV).run
9
+ cli = Spinach::Cli.new(ARGV)
10
+ cli.init_reporter
11
+ exit cli.run
@@ -32,7 +32,7 @@ Feature "Error reporting" do
32
32
 
33
33
  Then 'I should see the error count along with their messages' do
34
34
  check_error_messages
35
- all_stdout.wont_match /gems.*minitest.*assert_equal/
35
+ all_stderr.wont_match /gems.*minitest.*assert_equal/
36
36
  end
37
37
 
38
38
  Then 'I should see the error count along with their messages and backtrace' do
@@ -41,16 +41,12 @@ Feature "Error reporting" do
41
41
  end
42
42
 
43
43
  private
44
-
45
44
  def check_error_messages
46
- all_stdout.must_include "Error summary"
47
- all_stdout.must_match /errors.*This scenario will fail.*line 4/
45
+ all_stderr.must_match /Failures \(1\)/
48
46
  end
49
47
 
50
48
  def check_backtrace
51
- all_stdout.must_include "Error summary"
52
- all_stdout.must_match /errors.*This scenario will fail.*line 4/
53
- all_stdout.must_match /gems.*minitest.*assert_equal/
49
+ all_stderr.must_match /Failures \(1\)/
50
+ all_stderr.must_match /gems.*minitest.*assert_equal/
54
51
  end
55
-
56
52
  end
@@ -46,5 +46,4 @@ Feature "Exit status" do
46
46
  Then "the exit status should be 1" do
47
47
  last_exit_status.must_equal 1
48
48
  end
49
-
50
49
  end
@@ -27,7 +27,6 @@ Feature "Feature name guessing" do
27
27
  end
28
28
 
29
29
  Then 'I want "MyCoolFeature" class to be used to run it' do
30
- all_stderr.must_be_empty
30
+ last_exit_status.must_equal 0
31
31
  end
32
-
33
32
  end
@@ -5,10 +5,8 @@ module Integration
5
5
  include Aruba::Api
6
6
 
7
7
  private
8
-
9
8
  def run_feature(command, options=nil)
10
9
  run "../../bin/spinach #{command} #{options}"
11
10
  end
12
-
13
11
  end
14
12
  end
@@ -4,8 +4,7 @@ require_relative 'feature'
4
4
 
5
5
  module Spinach
6
6
  class Feature
7
-
8
- # Spinach's capybara module integrates capybara into all features
7
+ # Spinach's capybara module makes Capybara DSL available in all features.
9
8
  #
10
9
  # @example
11
10
  # require 'spinach/capybara'
@@ -16,20 +15,21 @@ module Spinach
16
15
  # end
17
16
  #
18
17
  module Capybara
18
+ # Enhances a Feature with Capybara goodness.
19
+ #
20
+ # @param [Class] base
21
+ # The host class.
22
+ #
23
+ # @api public
19
24
  def self.included(base)
20
25
  base.class_eval do
21
26
  include ::Capybara::DSL
22
- include InstanceMethods
23
27
 
24
- def after
28
+ after_scenario do
25
29
  ::Capybara.current_session.reset! if ::Capybara.app
26
30
  end
27
31
  end
28
32
  end
29
- module InstanceMethods
30
- end
31
33
  end
32
34
  end
33
35
  end
34
-
35
- Spinach::Feature.send(:include, Spinach::Feature::Capybara)
data/lib/spinach/cli.rb CHANGED
@@ -2,21 +2,24 @@ require 'optparse'
2
2
 
3
3
  module Spinach
4
4
  # The cli is a class responsible of handling all the command line interface
5
- # logic
5
+ # logic.
6
6
  #
7
7
  class Cli
8
8
  # @param [Array<String>] arguments
9
- # the command line arguments
9
+ # The command line arguments
10
+ #
11
+ # @api public
10
12
  def initialize(args = ARGV)
11
13
  @args = args
12
14
  end
13
15
 
14
- # Runs all the feature
16
+ # Runs all the features.
17
+ #
18
+ # @return [true, false]
19
+ # The exit status - true for success, false for failure.
15
20
  #
16
- # @return [Boolean]
17
- # the exit status - true for success, false for failure
21
+ # @api public
18
22
  def run
19
- init_reporter
20
23
  parse_options
21
24
  features = if @args.any?
22
25
  @args
@@ -26,42 +29,51 @@ module Spinach
26
29
  Spinach::Runner.new(features).run
27
30
  end
28
31
 
29
- # Inits the reporter with a default one
32
+ # Inits the reporter with a default one.
33
+ #
34
+ # @api public
30
35
  def init_reporter
31
- Spinach.config.default_reporter =
36
+ reporter =
32
37
  Spinach::Reporter::Stdout.new(options[:reporter])
38
+ Spinach.config.default_reporter = reporter
39
+ reporter.bind
33
40
  end
34
41
 
35
- # Returns a hash of options, separated by its type:
42
+ # @return [Hash]
43
+ # A hash of options separated by its type.
36
44
  #
37
45
  # @example
38
- # {
39
- # reporter: { backtrace: true }
40
- # }
46
+ # Cli.new.options
47
+ # # => { reporter: { backtrace: true } }
41
48
  #
42
- # @return [Hash]
49
+ # @api public
43
50
  def options
44
51
  @options ||= parse_options
45
52
  end
46
53
 
47
- private
54
+ private
48
55
 
56
+ # Parses the arguments into options.
57
+ #
58
+ # @return [Hash]
59
+ # A hash of options separated by its type.
60
+ #
61
+ # @api private
49
62
  def parse_options
50
63
  reporter_options = {}
51
64
  reporter_options[:backtrace] = false
52
65
 
53
66
  OptionParser.new do |opts|
54
- opts.on('-b', '--backtrace', "Show backtrace of errors") do |v|
67
+ opts.on('-b', '--backtrace', 'Show backtrace of errors') do |v|
55
68
  reporter_options[:backtrace] = v
56
69
  end
57
- opts.on_tail('--version', "Show version") do
70
+ opts.on_tail('--version', 'Show version') do
58
71
  puts Spinach::VERSION
59
72
  exit
60
73
  end
61
74
  end.parse!(@args)
75
+
62
76
  {reporter: reporter_options}
63
77
  end
64
-
65
-
66
78
  end
67
79
  end
@@ -1,10 +1,9 @@
1
1
  module Spinach
2
-
3
2
  # Accesses spinach config. Allows you to configure several runtime options,
4
3
  # like the step definitions path.
5
4
  #
6
- # @return [Spinach::Config]
7
- # the config object
5
+ # @return [Config]
6
+ # The config object
8
7
  #
9
8
  # @example
10
9
  # Spinach.config[:step_definitions_path]
@@ -12,6 +11,7 @@ module Spinach
12
11
  # Spinach.config[:step_definitions_path] = 'integration/steps'
13
12
  # # => 'integration/steps'
14
13
  #
14
+ # @api public
15
15
  def self.config
16
16
  @config ||= Config.new
17
17
  end
@@ -22,16 +22,24 @@ module Spinach
22
22
  class Config
23
23
  attr_writer :step_definitions_path, :default_reporter, :support_path
24
24
 
25
- # The "step definitions path" helds the place where your feature classes
25
+ # The "step definitions path" holds the place where your feature classes
26
26
  # will be searched for. Defaults to 'features/steps'
27
27
  #
28
+ # @return [String]
29
+ # The step definitions path.
30
+ #
31
+ # @api public
28
32
  def step_definitions_path
29
33
  @step_definitions_path || 'features/steps'
30
34
  end
31
35
 
32
- # The "support path" helds the place where your can put your configuration
33
- # files
36
+ # The "support path" helds the place where you can put your configuration
37
+ # files.
38
+ #
39
+ # @return [String]
40
+ # The support file path.
34
41
  #
42
+ # @api public
35
43
  def support_path
36
44
  @support_path || 'features/support'
37
45
  end
@@ -46,22 +54,33 @@ module Spinach
46
54
 
47
55
  # Allows you to read the config object using a hash-like syntax.
48
56
  #
57
+ # @param [String] attribute
58
+ # The attribute to fetch.
59
+ #
49
60
  # @example
50
61
  # Spinach.config[:step_definitions_path]
51
- # # => 'features/steps'
62
+ # # => 'features/steps'
52
63
  #
64
+ # @api public
53
65
  def [](attribute)
54
66
  self.send(attribute)
55
67
  end
56
68
 
57
- # Allows you to set config properties using a hash-like syntax
69
+ # Allows you to set config properties using a hash-like syntax.
70
+ #
71
+ # @param [#to_s] attribute
72
+ # The attribute to set.
73
+ #
74
+ # @param [Object] value
75
+ # The value to set the attribute to.
58
76
  #
59
77
  # @example
60
78
  # Spinach.config[:step_definitions_path] = 'integration/steps'
61
79
  # # => 'integration/steps'
62
80
  #
63
- def []=(attribute, params)
64
- self.send("#{attribute}=", params)
81
+ # @api public
82
+ def []=(attribute, value)
83
+ self.send("#{attribute}=", value)
65
84
  end
66
85
  end
67
86
  end
data/lib/spinach/dsl.rb CHANGED
@@ -1,34 +1,53 @@
1
+ require 'hooks'
2
+
1
3
  module Spinach
2
- # Spinach DSL aims to provide an easy way to define steps and other domain
3
- # specific actions into your feature classes
4
+ # Spinach DSL aims to provide an easy way to define steps and hooks into your
5
+ # feature classes.
4
6
  #
5
7
  module DSL
6
-
8
+ # @param [Class] base
9
+ # The host class.
10
+ #
11
+ # @api public
7
12
  def self.included(base)
8
13
  base.class_eval do
9
14
  include InstanceMethods
10
15
  extend ClassMethods
16
+ include Hooks
17
+
18
+ define_hook :before
19
+ define_hook :after
20
+ define_hook :before_scenario
21
+ define_hook :after_scenario
22
+ define_hook :before_step
23
+ define_hook :after_step
24
+
11
25
  end
12
26
  end
13
27
 
28
+ # Class methods to extend the host class.
29
+ #
14
30
  module ClassMethods
15
-
31
+ # The feature name.
32
+ attr_reader :feature_name
16
33
  # Defines an action to perform given a particular step literal.
17
34
  #
18
- # @param [String] step name
19
- # The step literal
35
+ # @param [String] step
36
+ # The step name.
37
+ #
20
38
  # @param [Proc] block
21
- # action to perform in that step
39
+ # Action to perform in that step.
22
40
  #
23
41
  # @example
24
- # class MyFeature << Spinach::Feature
42
+ # class MyFeature < Spinach::Feature
25
43
  # When "I go to the toilet" do
26
44
  # @sittin_on_the_toilet.must_equal true
27
45
  # end
28
46
  # end
29
47
  #
30
- def Given(string, &block)
31
- define_method(string, &block)
48
+ # @api public
49
+ def Given(step, &block)
50
+ define_method(step, &block)
32
51
  end
33
52
 
34
53
  alias_method :When, :Given
@@ -36,39 +55,44 @@ module Spinach
36
55
  alias_method :And, :Given
37
56
  alias_method :But, :Given
38
57
 
39
- # Defines this feature's name
58
+ # Sets the feature name.
59
+ #
60
+ # @param [String] name
61
+ # The name.
40
62
  #
41
63
  # @example
42
64
  # class MyFeature < Spinach::Feature
43
65
  # feature "Satisfy needs"
44
66
  # end
45
67
  #
68
+ # @api public
46
69
  def feature(name)
47
70
  @feature_name = name
48
71
  end
49
-
50
- # @return [String] this feature's name
51
- #
52
- attr_reader :feature_name
53
72
  end
54
73
 
74
+ # Instance methods to include in the host class.
75
+ #
55
76
  module InstanceMethods
56
-
57
- # Execute a given step.
77
+ # Executes a given step.
58
78
  #
59
79
  # @param [String] step
60
- # the step to execute
80
+ # The step name to execute.
81
+ #
82
+ # @return [String]
83
+ # The file and line where the step was defined.
61
84
  #
85
+ # @api public
62
86
  def execute_step(step)
63
87
  if self.respond_to?(step)
64
88
  self.send(step)
65
89
  else
66
- raise Spinach::StepNotDefinedException.new(
67
- self, step
68
- )
90
+ raise Spinach::StepNotDefinedException.new(self, step)
69
91
  end
70
92
  end
71
93
 
94
+ # @return [String]
95
+ # The feature name.
72
96
  def name
73
97
  self.class.feature_name
74
98
  end
@@ -1,28 +1,41 @@
1
1
  module Spinach
2
- # This class represents the feature raised when Spniach can't find a class
2
+ # This class represents the exception raised when Spinach can't find a class
3
3
  # for a feature.
4
+ #
4
5
  class FeatureNotFoundException < StandardError
6
+ # @param [Array] options
7
+ # An array consisting of the missing class and the feature.
8
+ #
9
+ # @api pulic
5
10
  def initialize(options)
6
- @not_found_class = options.first
7
- @feature = options.last
11
+ @missing_class, @feature = options
8
12
  end
9
13
 
14
+ # @return [String]
15
+ # A custom message when a feature class is not found.
16
+ #
17
+ # @api public
10
18
  def message
11
- "Could not find class for `#{@feature}` feature. Please create a #{@not_found_class}.rb file at #{Spinach.config[:step_definitions_path]}"
19
+ "Could not find class for `#{@feature}` feature. Please create a #{@missing_class}.rb file at #{Spinach.config[:step_definitions_path]}"
12
20
  end
13
21
  end
14
22
 
15
- # This class represents the step raised when Spinach can't find a step for a
16
- # Scenario.
23
+ # This class represents the exception raised when Spinach can't find a step
24
+ # for a {Scenario}.
17
25
  #
18
26
  class StepNotDefinedException < StandardError
19
-
20
27
  attr_reader :feature, :step
21
28
 
29
+ # @param [Feature] feature
30
+ # The container feature.
31
+ #
32
+ # @param [Hash] step
33
+ # The missing step.
34
+ #
35
+ # @api pulic
22
36
  def initialize(feature, step)
23
37
  @feature = feature
24
38
  @step = step
25
39
  end
26
40
  end
27
-
28
41
  end
@@ -2,21 +2,25 @@ require 'minitest/spec'
2
2
  MiniTest::Spec.new nil
3
3
 
4
4
  module Spinach
5
- # The feature class is the class where all the features must inherit from.
5
+ # The feature class is the class which all the features must inherit from.
6
6
  #
7
7
  class Feature
8
8
  include DSL
9
9
  include MiniTest::Assertions
10
10
 
11
- def before; end;
12
- def after; end;
13
-
11
+ # Registers the feature class for later use.
12
+ #
13
+ # @param [Class] base
14
+ # The host class.
15
+ #
16
+ # @api public
14
17
  def self.inherited(base)
15
18
  Spinach.features << base
16
19
  end
17
20
  end
18
21
  end
19
22
 
23
+ # Syntactic sugar. Define the "Feature do" syntax.
20
24
  Object.send(:define_method, :Feature) do |name, &block|
21
25
  Class.new(Spinach::Feature) do
22
26
  feature name
@@ -2,29 +2,35 @@ require 'gherkin'
2
2
  require 'gherkin/formatter/json_formatter'
3
3
 
4
4
  module Spinach
5
- # Spinach's parser uses Gherkin in the guts to extract all the information
6
- # needed from the plain feature definition.
5
+ # Parser leverages Gherkin to parse the feature definition.
7
6
  #
8
7
  class Parser
9
-
10
8
  # @param [String] filename
11
- # the file to parse
9
+ # The filename to parse.
12
10
  #
11
+ # @api public
13
12
  def initialize(filename)
14
13
  @filename = filename
15
14
  @formatter = Gherkin::Formatter::JSONFormatter.new(nil)
16
15
  @parser = Gherkin::Parser::Parser.new(@formatter)
17
16
  end
18
17
 
19
- # Gets the plain text content of the feature file.
18
+ # Gets the plain text content out of the feature file.
19
+ #
20
20
  # @return [String]
21
- # the plain feature content
21
+ # The plain feature content.
22
22
  #
23
+ # @api public
23
24
  def content
24
25
  File.read(@filename)
25
26
  end
26
27
 
27
- # @return [Hash] the parsed gerkin output
28
+ # Parses the feature file and returns an AST as a Hash.
29
+ #
30
+ # @return [Hash]
31
+ # The parsed Gherkin output.
32
+ #
33
+ # @api public
28
34
  def parse
29
35
  @parser.parse(content, @filename, __LINE__-1)
30
36
  @formatter.gherkin_object