gherkin_system 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 932f9e7927f4669a1dccd6af66d25a9f85fa446493d8612a65138558170d569d
4
- data.tar.gz: 112171a83d2083bc8a8d1a68412e54dc4e0af3a43afdaaa2c653821df56a1080
3
+ metadata.gz: 02e33f186f5d635c9b55fce86ad676377685b1e791d6df0e65eb4289660f7f3a
4
+ data.tar.gz: c4d95982a87a4f4e256549e82fe6b9259881852769967482911f7b92af029691
5
5
  SHA512:
6
- metadata.gz: a4a601d9c86be652601a73aa4fa5a88b5db61dbff738316983ac8596e9cb7f5abe7c1ecd5d3111e9c6d3e1eeb2010d77f3270926d8ca91a8880ab65ec223d7f4
7
- data.tar.gz: b39ecb4ff0463c325bcef5d0bd151d1d0cfc49a7d7d23f22e3f70322b9404cf6d9f074498f6bb6f09c56543fabed0cfe0f3a376587d09e8b83731590f2987bfd
6
+ metadata.gz: 1618d7d665631a694f721c23047c2d44b94bf3acd0d86ae8f50f9b845cbd0b4941d19ffe300a5d09ba9530d72c97d1e6f43a289a7647dbcef805942c95239e49
7
+ data.tar.gz: 7d6a37e09d9a91fb390bab01f39b2eef4ad022cac9bca61d27aef9938712f8f9afb79df777a135c89c6935e199747adfd0daa496493ae08671089fec48095001
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.1] - 2026-10-04
11
+
12
+ ### Added
13
+
14
+ - `bin/rails g gherkin_system:install` scaffolds the loader, `features/system/`, and `test/support/gherkin/`
15
+ - `bin/rails g gherkin_system:feature NAME` and `bin/rails g gherkin_system:steps NAME`
16
+
17
+ ### Fixed
18
+
19
+ - Step assertion failures (`flunk` / `assert`) now wrap in `StepFailure` with feature, step, and line
20
+ - `StepFailure` subclasses `Minitest::Assertion` so Capybara and other step errors report as Failures
21
+ - Failure messages include `bin/rails gherkin path/to.feature:LINE` for rerun
22
+ - Directory paths in `bin/rails gherkin PATH` select features under that directory; unmatched path/line filters raise instead of compiling nothing
23
+ - Nested step generators create the enclosing namespaces, so generated files load without existing modules
24
+ - Step skips remain Minitest skips, and wrapped failures appear in Minitest summary counts
25
+ - Generator tests remove their temporary Ruby files after each test, so test order cannot cause RuboCop failures
26
+
10
27
  ## [0.1.0] - 2026-10-03
11
28
 
12
29
  ### Initial Release
data/README.md CHANGED
@@ -13,6 +13,15 @@ Gherkin describes behavior. Rails owns execution. Each scenario becomes a Minite
13
13
  gem "gherkin_system"
14
14
  ```
15
15
 
16
+ ```sh
17
+ bundle install
18
+ bin/rails g gherkin_system:install
19
+ bin/rails g gherkin_system:feature checkout
20
+ bin/rails g gherkin_system:steps checkout
21
+ ```
22
+
23
+ `install` creates `test/system/gherkin_test.rb`, `features/system/`, and `test/support/gherkin/`. `feature` and `steps` add a `.feature` file and a steps module; wire the module into the loader with `require_relative` and `config.include_steps`.
24
+
16
25
  ## Agent skill
17
26
 
18
27
  For agents writing Gherkin scenarios in a Rails app, install the skill with [`npx skills`](https://www.skills.sh/docs/cli):
@@ -25,16 +34,24 @@ Use `-g` for a global install, `-a` to target specific agents, and `-y` to skip
25
34
 
26
35
  ## Configuration
27
36
 
37
+ Configure in the loader created by the install generator:
38
+
28
39
  ```ruby
29
- # config/initializers/gherkin_system.rb
40
+ # test/system/gherkin_test.rb
41
+ require "application_system_test_case"
42
+ require "gherkin_system/rails"
43
+
30
44
  GherkinSystem.configure do |config|
31
45
  config.features = Rails.root.join("features/system/**/*.feature")
32
- config.base_test_class = ApplicationSystemTestCase
33
46
  config.include_steps AuthenticationSteps
34
47
  config.strict = true
35
48
  end
49
+
50
+ GherkinSystem.load!(base: ApplicationSystemTestCase)
36
51
  ```
37
52
 
53
+ Generated tests subclass the class you pass. The gem does not install its own system-test superclass.
54
+
38
55
  | Option | ENV variable | Default |
39
56
  | --- | --- | --- |
40
57
  | `features` | | `features/system/**/*.feature` |
@@ -51,18 +68,6 @@ GHERKIN_TAGS="@critical and not @slow" bin/rails test:system
51
68
 
52
69
  Same filter via the CLI: `bin/rails gherkin --tags "@critical and not @slow"` (sets `GHERKIN_TAGS` for that run). Leave it unset to compile every scenario. If the env var stays set, later `bin/rails test:system` runs keep the filter.
53
70
 
54
- ## Rails setup
55
-
56
- ```ruby
57
- # test/system/gherkin_test.rb
58
- require "application_system_test_case"
59
- require "gherkin_system/rails"
60
-
61
- GherkinSystem.load!(base: ApplicationSystemTestCase)
62
- ```
63
-
64
- Generated tests subclass the class you pass. The gem does not install its own system-test superclass.
65
-
66
71
  ## Steps
67
72
 
68
73
  ```ruby
@@ -120,6 +125,7 @@ Rails `setup` runs first. Gherkin hooks and steps run inside the test method. Ra
120
125
 
121
126
  ```sh
122
127
  bin/rails gherkin
128
+ bin/rails gherkin features/system
123
129
  bin/rails gherkin features/system/checkout.feature
124
130
  bin/rails gherkin features/system/checkout.feature:42
125
131
  bin/rails gherkin --tags "@critical and not @slow"
@@ -127,6 +133,8 @@ bin/rails gherkin:check
127
133
  bin/rails gherkin:list
128
134
  ```
129
135
 
136
+ A directory path selects every feature under that directory. A path or line filter that matches nothing raises instead of running an empty suite.
137
+
130
138
  `gherkin:check` parses features and resolves steps. It does not start a browser. `bin/rails test:system` and `bin/rails test:all` run the generated tests as normal Minitest tests.
131
139
 
132
140
  Tag filters use [Cucumber tag expressions](https://github.com/cucumber/tag-expressions).
@@ -150,7 +158,7 @@ end
150
158
  | `UndefinedStep` | No definition matches the step |
151
159
  | `AmbiguousStep` | More than one definition matches |
152
160
  | `DuplicateStep` | The same pattern is registered twice |
153
- | `StepFailure` | A step raised. `cause` is the original exception |
161
+ | `StepFailure` | A step raised (`Minitest::Assertion`). `cause` is the original exception. Message includes feature, step, line, and `bin/rails gherkin …` rerun |
154
162
 
155
163
  Undefined and ambiguous steps raise in `before_setup`, before the driver starts. The message names the feature, scenario, step, feature line, and a suggested definition.
156
164
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module GherkinSystem
6
+ module Generators
7
+ # Creates a Gherkin feature under features/system.
8
+ class FeatureGenerator < Rails::Generators::NamedBase
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates a Gherkin feature file under features/system"
12
+
13
+ def create_feature
14
+ template "feature.feature.tt", File.join("features/system", "#{file_path}.feature")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ Feature: <%= human_name %>
2
+
3
+ Scenario: Replace with the condition and result
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module GherkinSystem
6
+ module Generators
7
+ # Scaffolds the loader, feature directory, and steps support directory.
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates gherkin_system loader, features/system, and test/support/gherkin"
12
+
13
+ def create_loader
14
+ template "gherkin_test.rb", "test/system/gherkin_test.rb"
15
+ end
16
+
17
+ def create_features_directory
18
+ empty_directory "features/system"
19
+ create_file "features/system/.keep", ""
20
+ end
21
+
22
+ def create_steps_directory
23
+ empty_directory "test/support/gherkin"
24
+ create_file "test/support/gherkin/.keep", ""
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "application_system_test_case"
4
+ require "gherkin_system/rails"
5
+
6
+ GherkinSystem.configure do |config|
7
+ # config.include_steps YourSteps
8
+ end
9
+
10
+ GherkinSystem.load!(base: ApplicationSystemTestCase)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module GherkinSystem
6
+ module Generators
7
+ # Creates a step definition module under test/support/gherkin.
8
+ class StepsGenerator < Rails::Generators::NamedBase
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates a gherkin_system steps module under test/support/gherkin"
12
+
13
+ def create_steps
14
+ template "steps.rb.tt", File.join("test/support/gherkin", "#{file_path}_steps.rb")
15
+ end
16
+
17
+ def remind_loader_wiring
18
+ say "Remember to require and include #{steps_module_name} in test/system/gherkin_test.rb", :green
19
+ end
20
+
21
+ private
22
+
23
+ def steps_module_name
24
+ "#{class_name}Steps"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% namespaces = class_name.split("::")[0...-1] -%>
4
+ <% namespaces.each_with_index do |namespace, index| -%>
5
+ <%= " " * index %>module <%= namespace %>
6
+ <% end -%>
7
+ <%= " " * namespaces.size %>module <%= class_name.demodulize %>Steps
8
+ <%= " " * namespaces.size %> extend GherkinSystem::Steps
9
+
10
+ <%= " " * namespaces.size %> # Given("…") do
11
+ <%= " " * namespaces.size %> # end
12
+ <%= " " * namespaces.size %>end
13
+ <% namespaces.size.times.reverse_each do |index| -%>
14
+ <%= " " * index %>end
15
+ <% end -%>
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "minitest"
4
+
3
5
  module GherkinSystem
4
6
  # Base error for gherkin_system failures.
5
7
  class Error < StandardError; end
@@ -13,6 +15,46 @@ module GherkinSystem
13
15
  # Raised when the same step pattern is registered twice.
14
16
  class DuplicateStep < Error; end
15
17
 
18
+ # Formats a bin/rails gherkin rerun hint for a scenario location.
19
+ module Rerun
20
+ module_function
21
+
22
+ # @param scenario [Scenario]
23
+ # @param line [Integer, nil]
24
+ # @return [Array<String>]
25
+ def footer(scenario, line = scenario.line)
26
+ ["", "Rerun:", " bin/rails gherkin #{location(scenario, line)}"]
27
+ end
28
+
29
+ # @param scenario [Scenario]
30
+ # @param line [Integer, nil]
31
+ # @return [String]
32
+ def location(scenario, line = scenario.line)
33
+ "#{display_path(scenario.uri)}:#{line}"
34
+ end
35
+
36
+ # @param uri [String]
37
+ # @return [String]
38
+ def display_path(uri)
39
+ path = uri.to_s
40
+ root = project_root
41
+ return path if root.nil? || root.empty?
42
+
43
+ absolute = File.expand_path(path)
44
+ prefix = "#{File.expand_path(root)}/"
45
+ return absolute.delete_prefix(prefix) if absolute.start_with?(prefix)
46
+
47
+ path
48
+ end
49
+
50
+ # @return [String, nil]
51
+ def project_root
52
+ return Rails.root.to_s if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
53
+
54
+ Dir.pwd
55
+ end
56
+ end
57
+
16
58
  # Raised when no step definition matches.
17
59
  class UndefinedStep < Error
18
60
  # @param scenario [Scenario]
@@ -27,11 +69,12 @@ module GherkinSystem
27
69
  "",
28
70
  " #{step.keyword} #{step.text}",
29
71
  "",
30
- "#{scenario.uri}:#{step.line}",
72
+ "#{Rerun.display_path(scenario.uri)}:#{step.line}",
31
73
  "",
32
74
  "Suggested definition:",
33
75
  "",
34
- Snippet.for(step)
76
+ Snippet.for(step),
77
+ *Rerun.footer(scenario)
35
78
  ].join("\n")
36
79
  end
37
80
  end
@@ -52,16 +95,18 @@ module GherkinSystem
52
95
  "",
53
96
  " #{step.keyword} #{step.text}",
54
97
  "",
55
- "#{scenario.uri}:#{step.line}",
98
+ "#{Rerun.display_path(scenario.uri)}:#{step.line}",
56
99
  "",
57
100
  "Matching definitions:",
58
- *locations
101
+ *locations,
102
+ *Rerun.footer(scenario)
59
103
  ].join("\n")
60
104
  end
61
105
  end
62
106
 
63
107
  # Wraps a step exception with the feature and definition locations.
64
- class StepFailure < Error
108
+ # Subclasses Minitest::Assertion so wrapped failures report as Failures.
109
+ class StepFailure < Minitest::Assertion
65
110
  # @return [Exception] the original failure
66
111
  attr_reader :cause
67
112
 
@@ -83,13 +128,24 @@ module GherkinSystem
83
128
  "FAILED",
84
129
  "",
85
130
  "#{step.keyword} #{step.text}",
86
- "#{scenario.uri}:#{step.line}",
131
+ "#{Rerun.display_path(scenario.uri)}:#{step.line}",
87
132
  "",
88
133
  cause.message.to_s,
89
134
  "",
90
135
  "Step definition:",
91
- definition.location_label
136
+ definition.location_label,
137
+ *Rerun.footer(scenario)
92
138
  ].join("\n")
93
139
  end
94
140
  end
141
+
142
+ # Minitest counts exact exception classes. Include our assertion subclass.
143
+ module StepFailureReporting
144
+ def report
145
+ super
146
+ self.failures += results.count { |result| result.failure.is_a?(StepFailure) }
147
+ end
148
+ end
95
149
  end
150
+
151
+ Minitest::StatisticsReporter.prepend(GherkinSystem::StepFailureReporting)
@@ -54,7 +54,7 @@ module GherkinSystem
54
54
  run_before
55
55
  run_steps
56
56
  @run.status = :passed
57
- rescue StandardError => e
57
+ rescue Minitest::Assertion, StandardError => e
58
58
  @run.status = :failed
59
59
  @run.exception = e
60
60
  raise
@@ -73,9 +73,9 @@ module GherkinSystem
73
73
  def run_step(step)
74
74
  definition = registry.resolve(step, @scenario)
75
75
  @test.instance_exec(*arguments(definition, step), &definition.block)
76
- rescue UndefinedStep, AmbiguousStep
76
+ rescue UndefinedStep, AmbiguousStep, Minitest::Skip
77
77
  raise
78
- rescue StandardError => e
78
+ rescue Minitest::Assertion, StandardError => e
79
79
  raise StepFailure.new(@scenario, step, definition, e)
80
80
  end
81
81
 
@@ -17,7 +17,9 @@ module GherkinSystem
17
17
  # @return [Array<Scenario>]
18
18
  def scenarios
19
19
  selected = files.flat_map { |path| Parser.parse_file(path) }
20
- filter_line(filter_path(selected))
20
+ selected = filter_line(filter_path(selected))
21
+ ensure_matches!(selected)
22
+ selected
21
23
  end
22
24
 
23
25
  private
@@ -29,16 +31,14 @@ module GherkinSystem
29
31
  paths = Dir.glob(@glob)
30
32
  return paths if requested.nil? || requested.empty?
31
33
 
32
- expanded = File.expand_path(requested)
33
- paths.select { |path| File.expand_path(path) == expanded }
34
+ paths.select { |path| path_match?(path, requested) }
34
35
  end
35
36
 
36
37
  def filter_path(scenarios)
37
38
  requested = ENV.fetch("GHERKIN_FEATURES", nil)
38
39
  return scenarios if requested.nil? || requested.empty?
39
40
 
40
- expanded = File.expand_path(requested)
41
- scenarios.select { |scenario| File.expand_path(scenario.uri) == expanded }
41
+ scenarios.select { |scenario| path_match?(scenario.uri, requested) }
42
42
  end
43
43
 
44
44
  def filter_line(scenarios)
@@ -48,5 +48,38 @@ module GherkinSystem
48
48
  number = Integer(line)
49
49
  scenarios.select { |scenario| scenario.covers_line?(number) }
50
50
  end
51
+
52
+ def path_match?(path, requested)
53
+ expanded_path = File.expand_path(path)
54
+ expanded_requested = File.expand_path(requested)
55
+ return true if expanded_path == expanded_requested
56
+
57
+ return false unless File.directory?(expanded_requested)
58
+
59
+ prefix = expanded_requested.end_with?("/") ? expanded_requested : "#{expanded_requested}/"
60
+ expanded_path.start_with?(prefix)
61
+ end
62
+
63
+ def ensure_matches!(scenarios)
64
+ return if scenarios.any?
65
+ return unless filtered?
66
+
67
+ raise ConfigurationError, "No scenarios matched #{filter_description}"
68
+ end
69
+
70
+ def filtered?
71
+ feature = ENV.fetch("GHERKIN_FEATURES", nil)
72
+ line = ENV.fetch("GHERKIN_LINE", nil)
73
+ !(feature.nil? || feature.empty?) || !(line.nil? || line.empty?)
74
+ end
75
+
76
+ def filter_description
77
+ feature = ENV.fetch("GHERKIN_FEATURES", nil)
78
+ line = ENV.fetch("GHERKIN_LINE", nil)
79
+ return "#{feature}:#{line}" if feature && line
80
+ return feature if feature && !feature.empty?
81
+
82
+ "line #{line}"
83
+ end
51
84
  end
52
85
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module GherkinSystem
4
4
  # Gem version.
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.1"
6
6
  end
@@ -26,6 +26,9 @@ module Rails
26
26
  def list(*args)
27
27
  prepare!(args)
28
28
  scenarios.each { |scenario| puts scenario_line(scenario) }
29
+ rescue GherkinSystem::Error => e
30
+ warn e.message
31
+ exit!(1)
29
32
  end
30
33
 
31
34
  private
@@ -43,7 +43,7 @@ module GherkinSystem
43
43
  class AmbiguousStep < Error
44
44
  end
45
45
 
46
- class StepFailure < Error
46
+ class StepFailure < ::Minitest::Assertion
47
47
  attr_reader cause: Exception
48
48
  end
49
49
 
@@ -14,14 +14,19 @@ Do not add cucumber-rails, a Cucumber World, or a second test runner. Do not use
14
14
 
15
15
  ## Add a scenario
16
16
 
17
- 1. Write the feature under `features/system/**/*.feature`.
18
- 2. Bind steps in `test/support/gherkin/*_steps.rb`.
19
- 3. Require that file from `test/system/gherkin_test.rb` before `GherkinSystem.load!`.
20
- 4. Run `bin/rails gherkin:check`.
21
- 5. Run the scenario with `bin/rails gherkin path/to/file.feature:LINE`.
17
+ 1. Scaffold with `bin/rails g gherkin_system:feature NAME` and `bin/rails g gherkin_system:steps NAME` (or write the files by hand under `features/system/**/*.feature` and `test/support/gherkin/*_steps.rb`).
18
+ 2. Require the steps file from `test/system/gherkin_test.rb` before `GherkinSystem.load!`.
19
+ 3. Run `bin/rails gherkin:check`.
20
+ 4. Run the scenario with `bin/rails gherkin path/to/file.feature:LINE`.
22
21
 
23
22
  Create the loader once if it is missing:
24
23
 
24
+ ```sh
25
+ bin/rails g gherkin_system:install
26
+ ```
27
+
28
+ Then require step modules and register them before `load!`:
29
+
25
30
  ```ruby
26
31
  # test/system/gherkin_test.rb
27
32
  require "application_system_test_case"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gherkin_system
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Paluy
@@ -69,6 +69,20 @@ dependencies:
69
69
  - - "<"
70
70
  - !ruby/object:Gem::Version
71
71
  version: '12'
72
+ - !ruby/object:Gem::Dependency
73
+ name: minitest
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '5.0'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '5.0'
72
86
  description: gherkin_system turns Gherkin scenarios into Minitest methods on the application's
73
87
  ApplicationSystemTestCase. Rails owns execution. Gherkin describes behavior.
74
88
  email:
@@ -84,6 +98,12 @@ files:
84
98
  - LICENSE
85
99
  - LICENSE.txt
86
100
  - README.md
101
+ - lib/generators/gherkin_system/feature/feature_generator.rb
102
+ - lib/generators/gherkin_system/feature/templates/feature.feature.tt
103
+ - lib/generators/gherkin_system/install/install_generator.rb
104
+ - lib/generators/gherkin_system/install/templates/gherkin_test.rb
105
+ - lib/generators/gherkin_system/steps/steps_generator.rb
106
+ - lib/generators/gherkin_system/steps/templates/steps.rb.tt
87
107
  - lib/gherkin_system.rb
88
108
  - lib/gherkin_system/checker.rb
89
109
  - lib/gherkin_system/cli.rb
@@ -133,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
153
  - !ruby/object:Gem::Version
134
154
  version: '0'
135
155
  requirements: []
136
- rubygems_version: 4.0.20
156
+ rubygems_version: 3.6.9
137
157
  specification_version: 4
138
158
  summary: Compile Gherkin features into Rails system tests.
139
159
  test_files: []