chutney 2.0.3 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/.rufo +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +2 -1
  6. data/Rakefile +2 -0
  7. data/chutney.gemspec +10 -5
  8. data/config/chutney.yml +4 -0
  9. data/config/cucumber.yml +1 -0
  10. data/docs/usage/rules.md +7 -1
  11. data/exe/chutney +2 -0
  12. data/lib/chutney.rb +19 -19
  13. data/lib/chutney/configuration.rb +2 -0
  14. data/lib/chutney/formatter.rb +2 -0
  15. data/lib/chutney/formatter/json_formatter.rb +2 -0
  16. data/lib/chutney/formatter/pie_formatter.rb +2 -0
  17. data/lib/chutney/formatter/rainbow_formatter.rb +2 -0
  18. data/lib/chutney/issue.rb +2 -0
  19. data/lib/chutney/linter.rb +65 -61
  20. data/lib/chutney/linter/avoid_full_stop.rb +3 -1
  21. data/lib/chutney/linter/avoid_outline_for_single_example.rb +8 -6
  22. data/lib/chutney/linter/avoid_scripting.rb +6 -4
  23. data/lib/chutney/linter/avoid_typographers_quotes.rb +39 -0
  24. data/lib/chutney/linter/background_does_more_than_setup.rb +8 -6
  25. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +6 -3
  26. data/lib/chutney/linter/bad_scenario_name.rb +4 -2
  27. data/lib/chutney/linter/empty_feature_file.rb +10 -0
  28. data/lib/chutney/linter/file_name_differs_feature_name.rb +4 -2
  29. data/lib/chutney/linter/givens_after_background.rb +5 -6
  30. data/lib/chutney/linter/invalid_file_name.rb +2 -0
  31. data/lib/chutney/linter/invalid_step_flow.rb +7 -7
  32. data/lib/chutney/linter/missing_example_name.rb +7 -5
  33. data/lib/chutney/linter/missing_feature_description.rb +6 -3
  34. data/lib/chutney/linter/missing_feature_name.rb +5 -2
  35. data/lib/chutney/linter/missing_scenario_name.rb +3 -4
  36. data/lib/chutney/linter/missing_test_action.rb +3 -1
  37. data/lib/chutney/linter/missing_verification.rb +3 -1
  38. data/lib/chutney/linter/required_tags_starts_with.rb +2 -0
  39. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +11 -10
  40. data/lib/chutney/linter/scenario_names_match.rb +4 -3
  41. data/lib/chutney/linter/tag_used_multiple_times.rb +2 -0
  42. data/lib/chutney/linter/too_clumsy.rb +3 -1
  43. data/lib/chutney/linter/too_long_step.rb +4 -2
  44. data/lib/chutney/linter/too_many_different_tags.rb +4 -2
  45. data/lib/chutney/linter/too_many_steps.rb +4 -2
  46. data/lib/chutney/linter/too_many_tags.rb +2 -0
  47. data/lib/chutney/linter/unique_scenario_names.rb +3 -3
  48. data/lib/chutney/linter/unknown_variable.rb +13 -13
  49. data/lib/chutney/linter/unused_variable.rb +13 -13
  50. data/lib/chutney/linter/use_background.rb +17 -16
  51. data/lib/chutney/linter/use_outline.rb +8 -7
  52. data/lib/chutney/version.rb +3 -1
  53. data/lib/config/locales/en.yml +6 -0
  54. data/spec/chutney_spec.rb +2 -0
  55. data/spec/spec_helper.rb +2 -0
  56. metadata +47 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77747e8f00fa87d5f74dc5ecaf4e13863409165badd8008f331dbabd8e110950
4
- data.tar.gz: 82e91f1890b82706c58df10dd0a8e874d6e06d1e53a856d213f4efc46372c36c
3
+ metadata.gz: 3eb2bbfa506f0fde529c0365064ffe21e6f102e37d3790e138e09c972c07319d
4
+ data.tar.gz: 8951672afdbf61863d949ebce24862f203395a931228bd98a0fe1d401ad1544f
5
5
  SHA512:
6
- metadata.gz: 0c9c5e518850ec64e4caaab3c502145dd703bcab606121e3b912bf339b11bbd500da696e5dc23dbbabeda0e93066e02897008621472cbfa7ae3a20b3f20b8262
7
- data.tar.gz: 959ff63b8b0100dcf5e90d2d7dd96d4774c605617edaa17ef687376d2236644b858de3240add2eda4c98b285ea4c19aee98e51e20c4081acd3dc769f002433b1
6
+ metadata.gz: d41e8f3694940cb023fd03192b973a3778366e2745b544e8003f8d2f02c65285de6c7e9b8e254e89ce714732474c0837db8487da0eefbaba77ed405a02cf4f57
7
+ data.tar.gz: 1a3f433eb45190171bad88ca533e65f533f59ff5d2c2654a3da41b4f585b3abab3a6814824b0925ea0973ba76e331ce2bb9ab9345cd2f73ae1d242f64641eaff
@@ -8,7 +8,7 @@
8
8
 
9
9
  # Offense count: 1
10
10
  Metrics/AbcSize:
11
- Max: 16
11
+ Enabled: false
12
12
 
13
13
  # Offense count: 1
14
14
  # Configuration parameters: CountComments.
@@ -55,4 +55,10 @@ Layout/TrailingWhitespace:
55
55
  Enabled: false
56
56
 
57
57
  Style/FrozenStringLiteralComment:
58
+ Enabled: true
59
+
60
+ Style/StringConcatenation:
58
61
  Enabled: false
62
+
63
+ AllCops:
64
+ NewCops: enable
data/.rufo ADDED
@@ -0,0 +1 @@
1
+ quote_style :single
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/README.md CHANGED
@@ -12,10 +12,11 @@
12
12
  <div align="center">
13
13
 
14
14
  [![Gem Version](https://badge.fury.io/rb/chutney.svg)](https://badge.fury.io/rb/chutney)
15
+ [![Downloads](https://img.shields.io/gem/dt/chutney)](https://rubygems.org/gems/chutney)
15
16
  ![CircleCI branch](https://img.shields.io/circleci/project/github/BillyRuffian/chutney/master.svg?style=flat-square)
16
17
  [![CodeFactor](https://www.codefactor.io/repository/github/billyruffian/chutney/badge?style=flat-square)](https://www.codefactor.io/repository/github/billyruffian/chutney)
17
18
  ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/BillyRuffian/chutney.svg?style=flat-square)
18
19
 
19
20
  </div>
20
21
 
21
- Read the documentation [here](https://billyruffian.github.io/chutney/).
22
+ Read the documentation [here](https://billyruffian.github.io/chutney/).
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake/testtask'
2
4
 
3
5
  task default: :build
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Disable rubocop checks for the .gemspec
2
4
  # I'll take the output from 'bundle gem new' to be authoritative
3
5
  # rubocop:disable all
@@ -12,9 +14,9 @@ Gem::Specification.new do |spec|
12
14
  spec.authors = ['Nigel Brookes-Thomas', 'Stefan Rohe', 'Nishtha Argawal', 'John Gluck']
13
15
  spec.email = ['nigel@brookes-thomas.co.uk']
14
16
 
15
- spec.summary = 'A linter for English language Gherkin'
17
+ spec.summary = 'A linter for multi-lingual Gherkin'
16
18
  spec.description = 'A linter for your Cucumber features. ' \
17
- 'It supports any spoken language Cucumber v3 supports.'
19
+ 'It supports any spoken language Cucumber supports.'
18
20
 
19
21
  spec.homepage = 'https://billyruffian.github.io/chutney/'
20
22
  spec.license = 'MIT'
@@ -43,18 +45,21 @@ Gem::Specification.new do |spec|
43
45
  spec.require_paths = ['lib']
44
46
 
45
47
  spec.add_runtime_dependency 'amatch', '~> 0.4.0'
46
- spec.add_runtime_dependency 'gherkin', '>= 5.1', '< 9.1'
48
+ spec.add_runtime_dependency 'cuke_modeler', '~> 3.3'
49
+ spec.add_runtime_dependency 'gherkin', '>= 5.1.0', '< 9.1'
47
50
  spec.add_runtime_dependency 'i18n', '~> 1.8.2'
48
51
  spec.add_runtime_dependency 'pastel', '~> 0.7'
49
52
  spec.add_runtime_dependency 'tty-pie', '~> 0.3'
50
53
 
51
54
 
52
55
  spec.add_development_dependency 'coveralls', '~> 0.8'
53
- spec.add_development_dependency 'cucumber', '~> 3.0'
56
+ spec.add_development_dependency 'cucumber', '~> 5.1'
57
+ spec.add_development_dependency 'pry-byebug', '~> 3.0'
54
58
  spec.add_development_dependency 'rake', '~> 13.0'
55
59
  spec.add_development_dependency 'rerun', '~> 0.13'
56
60
  spec.add_development_dependency 'rspec-expectations', '~> 3.0'
57
- spec.add_development_dependency 'rubocop', '~> 0.82.0'
61
+ spec.add_development_dependency 'rubocop', '~> 0.89.0'
58
62
  spec.add_development_dependency 'rspec', '~> 3.8'
59
63
 
64
+ spec.required_ruby_version = '~> 2.6'
60
65
  end
@@ -4,12 +4,16 @@ AvoidFullStop:
4
4
  Enabled: true
5
5
  AvoidScripting:
6
6
  Enabled: true
7
+ AvoidTypographersQuotes:
8
+ Enabled: true
7
9
  BackgroundDoesMoreThanSetup:
8
10
  Enabled: true
9
11
  BackgroundRequiresMultipleScenarios:
10
12
  Enabled: true
11
13
  BadScenarioName:
12
14
  Enabled: true
15
+ EmptyFeatureFile:
16
+ Enabled: true
13
17
  FileNameDiffersFeatureName:
14
18
  Enabled: true
15
19
  GivensAfterBackground:
@@ -0,0 +1 @@
1
+ default: --publish-quiet
@@ -18,6 +18,9 @@ Chutney enforces its rules with the linters. These are:
18
18
  [AvoidScripting](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_scripting.feature)
19
19
  : You have a lot of steps, are you sure you're not scripting the scenario when you should be specifying the behaviour of the system?
20
20
 
21
+ [AvoidTypographersQuotes](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_typographers_quotes.feature)
22
+ : Cutting and pasting from Word documents? Is that pasting in curly-quotes instead of neutral ones you would type on a keyboard? Are you sure that's what you want?
23
+
21
24
  [BackgroundDoesMoreThanSetup](https://github.com/BillyRuffian/chutney/blob/master/features/background_does_more_than_setup.feature)
22
25
  : Background in feature files should only do setup activity and so they should only contain `Given` steps.
23
26
 
@@ -27,6 +30,9 @@ Chutney enforces its rules with the linters. These are:
27
30
  [BadScenarioName](https://github.com/BillyRuffian/chutney/blob/master/features/bad_scenario_name.feature)
28
31
  : You should avoid using words like 'test' or 'check' in your scenario names, instead you should define the behaviour of your system.
29
32
 
33
+ [EmptyFeatureFile](https://github.com/BillyRuffian/chutney/blob/master/features/empty_feature_file.feature)
34
+ : The feature should have content and should avoid committing empty features to repositories.
35
+
30
36
  [FileNameDiffersFeatureName](https://github.com/BillyRuffian/chutney/blob/master/features/file_name_differs_feature_name.feature)
31
37
  : The feature should have a name that follows the file name.
32
38
 
@@ -76,7 +82,7 @@ Chutney enforces its rules with the linters. These are:
76
82
  : This is a very long step. Consider writing it more concisely.
77
83
 
78
84
  [TooManyDifferentTags](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_different_tags.feature)
79
- : This feature has a lot of differnt tags.
85
+ : This feature has a lot of different tags.
80
86
 
81
87
  [TooManySteps](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_steps.feature)
82
88
  : This feature has a lot of steps. Consider writing it more concisely.
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'chutney'
3
5
  require 'chutney/formatter'
4
6
  require 'chutney/formatter/json_formatter'
@@ -1,12 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amatch'
4
+
2
5
  require 'chutney/configuration'
3
6
  require 'chutney/linter'
4
7
  require 'chutney/linter/avoid_full_stop'
5
8
  require 'chutney/linter/avoid_outline_for_single_example'
6
9
  require 'chutney/linter/avoid_scripting'
10
+ require 'chutney/linter/avoid_typographers_quotes'
7
11
  require 'chutney/linter/background_does_more_than_setup'
8
12
  require 'chutney/linter/background_requires_multiple_scenarios'
9
13
  require 'chutney/linter/bad_scenario_name'
14
+ require 'chutney/linter/empty_feature_file'
10
15
  require 'chutney/linter/file_name_differs_feature_name'
11
16
  require 'chutney/linter/givens_after_background'
12
17
  require 'chutney/linter/invalid_file_name'
@@ -31,9 +36,11 @@ require 'chutney/linter/unknown_variable'
31
36
  require 'chutney/linter/unused_variable'
32
37
  require 'chutney/linter/use_background'
33
38
  require 'chutney/linter/use_outline'
39
+
40
+ require 'cuke_modeler'
34
41
  require 'forwardable'
35
- require 'gherkin/dialect'
36
- require 'gherkin/parser'
42
+ # require 'gherkin/dialect'
43
+ # require 'gherkin/parser'
37
44
  require 'i18n'
38
45
  require 'set'
39
46
  require 'yaml'
@@ -43,9 +50,8 @@ module Chutney
43
50
  class ChutneyLint
44
51
  extend Forwardable
45
52
  attr_accessor :verbose
46
- attr_reader :files
47
- attr_reader :results
48
-
53
+ attr_reader :files, :results
54
+
49
55
  def_delegators :@files, :<<, :clear, :delete, :include?
50
56
 
51
57
  def initialize(*files)
@@ -56,7 +62,7 @@ module Chutney
56
62
 
57
63
  I18n.load_path << i18n_paths
58
64
  end
59
-
65
+
60
66
  def configuration
61
67
  unless @config
62
68
  default_file = [File.expand_path('..', __dir__), '**/config', 'chutney.yml']
@@ -65,11 +71,11 @@ module Chutney
65
71
  end
66
72
  @config
67
73
  end
68
-
74
+
69
75
  def configuration=(config)
70
76
  @config = config
71
77
  end
72
-
78
+
73
79
  def analyse
74
80
  files.each do |f|
75
81
  lint(f)
@@ -79,25 +85,19 @@ module Chutney
79
85
  # alias for non-british English
80
86
  # https://dictionary.cambridge.org/dictionary/english/analyse
81
87
  alias analyze analyse
82
-
88
+
83
89
  def linters
84
90
  @linters ||= Linter.descendants.filter { |l| configuration.dig(l.linter_name, 'Enabled') }
85
91
  end
86
-
92
+
87
93
  def linters=(*linters)
88
94
  @linters = linters
89
95
  end
90
-
96
+
91
97
  private
92
-
93
- def parse(text)
94
- @parser ||= Gherkin::Parser.new
95
- scanner = Gherkin::TokenScanner.new(text)
96
- @parser.parse(scanner)
97
- end
98
-
98
+
99
99
  def lint(file)
100
- parsed = parse(File.read(file))
100
+ parsed = CukeModeler::FeatureFile.new(file)
101
101
  linters.each do |linter_class|
102
102
  linter = linter_class.new(file, parsed, configuration[linter_class.linter_name])
103
103
  linter.lint
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
  module Chutney
3
5
  # gherkin_lint configuration object
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # base class for all formatters
3
5
  class Formatter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # Plain old JSON formatter
3
5
  class JSONFormatter < Formatter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
  require 'tty/pie'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
 
3
5
  module Chutney
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'term/ansicolor'
2
4
 
3
5
  module Chutney
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # gherkin utilities
4
+
2
5
  module Chutney
3
6
  # base class for all linters
4
7
  class Linter
5
8
  attr_accessor :issues
6
- attr_reader :filename
7
- attr_reader :configuration
9
+ attr_reader :filename, :configuration
8
10
 
9
11
  Lint = Struct.new(:message, :gherkin_type, :location, :feature, :scenario, :step, keyword_init: true)
10
12
 
@@ -17,8 +19,8 @@ module Chutney
17
19
  @filename = filename
18
20
  @issues = []
19
21
  @configuration = configuration
20
- language = @content.dig(:feature, :language) || 'en'
21
- @dialect = Gherkin::Dialect.for(language)
22
+ # language = @content.dig(:feature, :language) || 'en'
23
+ # @dialect = Gherkin::Dialect.for(language)
22
24
  end
23
25
 
24
26
  def lint
@@ -26,63 +28,71 @@ module Chutney
26
28
  end
27
29
 
28
30
  def and_word?(word)
29
- @dialect.and_keywords.include?(word)
31
+ dialect_word(:and).include?(word)
30
32
  end
31
33
 
32
34
  def background_word?(word)
33
- @dialect.background_keywords.include?(word)
35
+ dialect_word(:background).include?(word)
34
36
  end
35
37
 
36
38
  def but_word?(word)
37
- @dialect.but_keywords.include?(word)
39
+ dialect_word(:but).include?(word)
38
40
  end
39
41
 
40
42
  def examples_word?(word)
41
- @dialect.example_keywords.include?(word)
43
+ dialect_word(:examples).include?(word)
42
44
  end
43
45
 
44
46
  def feature_word?(word)
45
- @dialect.feature_keywords.include?(word)
47
+ dialect_word(:feature).include?(word)
46
48
  end
47
49
 
48
50
  def given_word?(word)
49
- @dialect.given_keywords.include?(word)
51
+ dialect_word(:given).include?(word)
50
52
  end
51
53
 
52
54
  def scenario_outline_word?(word)
53
- @dialect.scenario_outline_keywords.include?(word)
55
+ dialect_word(:scenarioOutline).include?(word)
54
56
  end
55
57
 
56
58
  def then_word?(word)
57
- @dialect.then_keywords.include?(word)
59
+ dialect_word(:then).include?(word)
58
60
  end
59
61
 
60
62
  def when_word?(word)
61
- @dialect.when_keywords.include?(word)
63
+ dialect_word(:when).include?(word)
64
+ end
65
+
66
+ def dialect_word(word)
67
+ CukeModeler::Parsing.dialects[dialect][word.to_s].map(&:strip)
68
+ end
69
+
70
+ def dialect
71
+ @content.feature&.parsing_data&.dig(:language) || 'en'
62
72
  end
63
73
 
64
74
  def tags_for(element)
65
- return [] unless element.include? :tags
66
-
67
- element[:tags].map { |tag| tag[:name][1..-1] }
75
+ element.tags.map { |tag| tag.name[1..-1] }
68
76
  end
69
77
 
70
- def add_issue(message, feature, scenario = nil, step = nil)
78
+ def add_issue(message, feature = nil, scenario = nil, item = nil)
71
79
  issues << Lint.new(
72
80
  message: message,
73
- gherkin_type: type(feature, scenario, step),
74
- location: location(feature, scenario, step),
75
- feature: feature[:name],
76
- scenario: scenario ? scenario[:name] : nil,
77
- step: step ? step[:text] : nil
81
+ gherkin_type: type(feature, scenario, item),
82
+ location: location(feature, scenario, item),
83
+ feature: feature&.name,
84
+ scenario: scenario&.name,
85
+ step: item&.parsing_data&.dig(:name)
78
86
  ).to_h
79
87
  end
80
88
 
81
89
  def location(feature, scenario, step)
82
90
  if step
83
- step[:location]
84
- else
85
- scenario ? scenario[:location] : feature[:location]
91
+ step.parsing_data[:location]
92
+ elsif scenario
93
+ scenario.parsing_data.dig(:scenario, :location) || scenario.parsing_data.dig(:background, :location)
94
+ else
95
+ feature ? feature.parsing_data[:location] : 0
86
96
  end
87
97
  end
88
98
 
@@ -96,77 +106,71 @@ module Chutney
96
106
 
97
107
  def feature
98
108
  if block_given?
99
- yield(@content[:feature]) if @content[:feature]
109
+ yield(@content.feature) if @content.feature
100
110
  else
101
- @content[:feature]
111
+ @content.feature
102
112
  end
103
113
  end
104
114
 
105
- def elements
115
+ def elements
116
+ return [] unless feature
117
+
106
118
  if block_given?
107
- feature[:children].each do |child|
119
+ feature.children.each do |child|
108
120
  next if off_switch?(child)
109
121
 
110
122
  yield(feature, child)
111
123
  end
112
124
  else
113
- feature[:children]
125
+ feature.children
114
126
  end
115
127
  end
116
-
128
+
117
129
  def off_switch?(element = feature)
118
- off_switch = element[:tags]
119
- .then { |tags| tags || [] }
120
- .filter { |tag| tag[:type] == :Tag }
121
- .filter { |tag| tag[:name] == "@disable#{linter_name}" }
122
- .count
123
- .positive?
130
+ off_switch = element.tags
131
+ .then { |tags| tags || [] }
132
+ .filter { |tag| tag[:type] == :Tag }
133
+ .filter { |tag| tag[:name] == "@disable#{linter_name}" }
134
+ .count
135
+ .positive?
124
136
  off_switch ||= off_switch?(feature) unless element == feature
125
137
  off_switch
126
138
  end
127
139
 
128
140
  def background
129
- if block_given?
130
- elements do |feature, child|
131
- next unless child[:type] == :Background
132
-
133
- yield(feature, child)
134
- end
141
+ if block_given?
142
+ yield(feature, feature&.background)
135
143
  else
136
- elements.filter { |child| child[:type] == :Background }
144
+ feature&.background
137
145
  end
138
146
  end
139
147
 
140
148
  def scenarios
141
149
  if block_given?
142
- elements do |feature, child|
143
- next unless %i[ScenarioOutline Scenario].include? child[:type]
144
-
145
- yield(feature, child)
150
+ feature&.tests&.each do |test|
151
+ yield(feature, test)
146
152
  end
153
+
147
154
  else
148
- elements.filter { |child| %i[ScenarioOutline Scenario].include? child[:type] }
155
+ feature&.tests
149
156
  end
150
157
  end
151
158
 
152
159
  def filled_scenarios
153
160
  if block_given?
154
161
  scenarios do |feature, scenario|
155
- next unless scenario.include? :steps
156
- next if scenario[:steps].empty?
162
+ next if scenario.steps.empty?
157
163
 
158
164
  yield(feature, scenario)
159
165
  end
160
166
  else
161
- scenarios.filter { |s| !s[:steps].empty? }
167
+ scenarios ? scenarios.filter { |s| !s.steps.empty? } : []
162
168
  end
163
169
  end
164
170
 
165
171
  def steps
166
- elements do |feature, child|
167
- next unless child.include? :steps
168
-
169
- child[:steps].each { |step| yield(feature, child, step) }
172
+ feature&.tests&.each do |t|
173
+ t.steps.each { |s| yield(feature, t, s) }
170
174
  end
171
175
  end
172
176
 
@@ -179,16 +183,16 @@ module Chutney
179
183
  end
180
184
 
181
185
  def render_step(step)
182
- value = "#{step[:keyword]}#{step[:text]}"
183
- value += render_step_argument step[:argument] if step.include? :argument
186
+ value = "#{step.keyword} #{step.text}"
187
+ value += render_step_argument(step.block) if step.block
184
188
  value
185
189
  end
186
190
 
187
191
  def render_step_argument(argument)
188
- return "\n#{argument[:content]}" if argument[:type] == :DocString
192
+ return "\n#{argument.content}" if argument.is_a?(CukeModeler::DocString)
189
193
 
190
- result = argument[:rows].map do |row|
191
- "|#{row[:cells].map { |cell| cell[:value] }.join '|'}|"
194
+ result = argument.rows.map do |row|
195
+ "|#{row.cells.map(&:value).join '|'}|"
192
196
  end.join "\n"
193
197
  "\n#{result}"
194
198
  end