chutney 2.2.1 → 3.0.0.beta.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/Gemfile +2 -0
  4. data/Rakefile +2 -0
  5. data/chutney.gemspec +8 -4
  6. data/config/cucumber.yml +1 -0
  7. data/exe/chutney +2 -0
  8. data/lib/chutney.rb +9 -11
  9. data/lib/chutney/configuration.rb +2 -0
  10. data/lib/chutney/formatter.rb +2 -0
  11. data/lib/chutney/formatter/json_formatter.rb +2 -0
  12. data/lib/chutney/formatter/pie_formatter.rb +2 -0
  13. data/lib/chutney/formatter/rainbow_formatter.rb +2 -0
  14. data/lib/chutney/issue.rb +2 -0
  15. data/lib/chutney/linter.rb +58 -59
  16. data/lib/chutney/linter/avoid_full_stop.rb +3 -1
  17. data/lib/chutney/linter/avoid_outline_for_single_example.rb +8 -6
  18. data/lib/chutney/linter/avoid_scripting.rb +6 -4
  19. data/lib/chutney/linter/avoid_typographers_quotes.rb +16 -14
  20. data/lib/chutney/linter/background_does_more_than_setup.rb +8 -6
  21. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +6 -3
  22. data/lib/chutney/linter/bad_scenario_name.rb +4 -2
  23. data/lib/chutney/linter/empty_feature_file.rb +2 -0
  24. data/lib/chutney/linter/file_name_differs_feature_name.rb +4 -2
  25. data/lib/chutney/linter/givens_after_background.rb +5 -6
  26. data/lib/chutney/linter/invalid_file_name.rb +2 -0
  27. data/lib/chutney/linter/invalid_step_flow.rb +7 -7
  28. data/lib/chutney/linter/missing_example_name.rb +7 -5
  29. data/lib/chutney/linter/missing_feature_description.rb +4 -3
  30. data/lib/chutney/linter/missing_feature_name.rb +3 -2
  31. data/lib/chutney/linter/missing_scenario_name.rb +3 -4
  32. data/lib/chutney/linter/missing_test_action.rb +3 -1
  33. data/lib/chutney/linter/missing_verification.rb +3 -1
  34. data/lib/chutney/linter/required_tags_starts_with.rb +2 -0
  35. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +11 -10
  36. data/lib/chutney/linter/scenario_names_match.rb +4 -3
  37. data/lib/chutney/linter/tag_used_multiple_times.rb +2 -0
  38. data/lib/chutney/linter/too_clumsy.rb +3 -1
  39. data/lib/chutney/linter/too_long_step.rb +4 -2
  40. data/lib/chutney/linter/too_many_different_tags.rb +4 -2
  41. data/lib/chutney/linter/too_many_steps.rb +4 -2
  42. data/lib/chutney/linter/too_many_tags.rb +2 -0
  43. data/lib/chutney/linter/unique_scenario_names.rb +3 -3
  44. data/lib/chutney/linter/unknown_variable.rb +13 -13
  45. data/lib/chutney/linter/unused_variable.rb +13 -13
  46. data/lib/chutney/linter/use_background.rb +17 -16
  47. data/lib/chutney/linter/use_outline.rb +8 -7
  48. data/lib/chutney/version.rb +3 -1
  49. data/spec/chutney_spec.rb +2 -0
  50. data/spec/spec_helper.rb +2 -0
  51. metadata +33 -12
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for avoid scripting
3
5
  class AvoidScripting < Linter
4
6
  def lint
5
7
  scenarios do |feature, scenario|
6
- when_steps = filter_when_steps(scenario[:steps])
8
+ when_steps = filter_when_steps(scenario.steps)
7
9
  whens = when_steps.count
8
10
  add_issue(I18n.t('linters.avoid_scripting', count: whens), feature, scenario, when_steps.last) if whens > 1
9
11
  end
@@ -11,9 +13,9 @@ module Chutney
11
13
 
12
14
  def filter_when_steps(steps)
13
15
  steps
14
- .drop_while { |step| !when_word?(step[:keyword]) }
15
- .then { |s| s.reverse.drop_while { |step| !then_word?(step[:keyword]) }.reverse }
16
- .then { |s| s.reject { |step| then_word?(step[:keyword]) } }
16
+ .drop_while { |step| !when_word?(step.keyword) }
17
+ .then { |s| s.reverse.drop_while { |step| !then_word?(step.keyword) }.reverse }
18
+ .then { |s| s.reject { |step| then_word?(step.keyword) } }
17
19
  end
18
20
  end
19
21
  end
@@ -1,37 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for avoid scripting
3
5
  class AvoidTypographersQuotes < Linter
4
6
  TYPOGRAPHER_QUOTES = ["\u201c", "\u201d", "\u2018", "\u2019"].map(&:encode)
5
7
 
6
8
  def lint
7
- scenarios do |_feature, scenario|
8
- lint_steps(scenario[:steps])
9
+ scenarios do |feature, scenario|
10
+ lint_steps(feature, scenario)
9
11
 
10
- example_count = scenario[:examples]&.length || 0
12
+ example_count = scenario.is_a?(CukeModeler::Outline) ? scenario.examples.length : 0
11
13
  next unless example_count.positive?
12
14
 
13
- lint_examples(scenario[:examples])
15
+ lint_examples(feature, scenario)
14
16
  end
15
17
  end
16
18
 
17
- def lint_steps(steps)
18
- steps.each do |step|
19
- issue(step) if TYPOGRAPHER_QUOTES.any? { |tq| step[:text].include? tq }
19
+ def lint_steps(feature, scenario)
20
+ scenario.steps.each do |step|
21
+ issue(feature, scenario, step) if TYPOGRAPHER_QUOTES.any? { |tq| step.text.include? tq }
20
22
  end
21
23
  end
22
24
 
23
- def lint_examples(examples)
24
- examples.each do |example|
25
- example[:tableBody].each do |body|
26
- body[:cells].each do |cell|
27
- issue(cell) if TYPOGRAPHER_QUOTES.any? { |tq| cell[:value].include? tq }
25
+ def lint_examples(feature, scenario)
26
+ scenario.examples.each do |example|
27
+ example.rows.each do |row|
28
+ row.cells.each do |cell|
29
+ issue(feature, scenario, cell) if TYPOGRAPHER_QUOTES.any? { |tq| cell.value.include? tq }
28
30
  end
29
31
  end
30
32
  end
31
33
  end
32
34
 
33
- def issue(location)
34
- add_issue(I18n.t('linters.avoid_typographers_quotes'), location)
35
+ def issue(feature, scenario, location)
36
+ add_issue(I18n.t('linters.avoid_typographers_quotes'), feature, scenario, location)
35
37
  end
36
38
  end
37
39
  end
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for background that does more than setup
3
5
  class BackgroundDoesMoreThanSetup < Linter
4
6
  def lint
5
7
  background do |feature, background|
6
- next unless background.key? :steps
7
-
8
- invalid_steps = background[:steps].select do |step|
9
- when_word?(step[:keyword]) || then_word?(step[:keyword])
8
+
9
+ invalid_steps = background&.steps&.select do |step|
10
+ when_word?(step.keyword) || then_word?(step.keyword)
10
11
  end
11
- next if invalid_steps.empty?
12
+
13
+ next if invalid_steps.nil? || invalid_steps.empty?
12
14
 
13
- add_issue(I18n.t('linters.background_does_more_than_setup'), feature, background)
15
+ add_issue(I18n.t('linters.background_does_more_than_setup'), feature, background, invalid_steps.first)
14
16
  end
15
17
  end
16
18
  end
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
4
6
  # service class for check that there are multiple scenarios once a background is used
5
7
  class BackgroundRequiresMultipleScenarios < Linter
6
- MESSAGE = 'Avoid using Background steps for just one scenario'.freeze
8
+ MESSAGE = 'Avoid using Background steps for just one scenario'
7
9
 
8
10
  def lint
9
11
  background do |feature, background|
10
- scenarios = feature[:children].reject { |element| element[:type] == :Background }
11
- next if scenarios.length >= 2
12
+ next unless background
13
+ next unless feature&.tests
14
+ next if feature.tests.length >= 2
12
15
 
13
16
  add_issue(I18n.t('linters.background_requires_multiple_scenarios'), feature, background)
14
17
  end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for bad scenario names
3
5
  class BadScenarioName < Linter
4
6
  def lint
5
7
  scenarios do |feature, scenario|
6
- next if scenario[:name].empty?
8
+ next if scenario.name.empty?
7
9
 
8
10
  bad = /\w*(test|verif|check)\w*/i
9
- match = scenario[:name].match(bad).to_a.first
11
+ match = scenario.name.match(bad).to_a.first
10
12
  add_issue(I18n.t('linters.bad_scenario_name', word: match), feature, scenario) if match
11
13
  end
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for Features that have no content
3
5
  class EmptyFeatureFile < Linter
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for file name differs feature name
3
5
  class FileNameDiffersFeatureName < Linter
4
6
  def lint
5
- return unless feature&.include?(:name)
7
+ return unless feature
6
8
 
7
9
  expected_feature_name = title_case(filename)
8
- return if ignore_whitespaces(feature[:name]).casecmp(ignore_whitespaces(expected_feature_name)) == 0
10
+ return if ignore_whitespaces(feature.name).casecmp(ignore_whitespaces(expected_feature_name)) == 0
9
11
 
10
12
  add_issue(I18n.t('linters.file_name_differs_feature_name', expected: expected_feature_name), feature)
11
13
  end
@@ -1,15 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for bad scenario names
3
5
  class GivensAfterBackground < Linter
4
6
  def lint
5
- return if background.nil?
6
- return if background.empty?
7
+ return unless background
7
8
 
8
9
  filled_scenarios do |feature, scenario|
9
- scenario[:steps].each do |step|
10
- if given_word?(step[:keyword])
11
- add_issue(I18n.t('linters.givens_after_background'), feature, scenario, step)
12
- end
10
+ scenario.steps.each do |step|
11
+ add_issue(I18n.t('linters.givens_after_background'), feature, scenario, step) if given_word?(step.keyword)
13
12
  end
14
13
  end
15
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for invalid step flow
3
5
  class InvalidStepFlow < Linter
4
6
  def lint
5
7
  filled_scenarios do |feature, scenario|
6
- steps = scenario[:steps].select { |step| !and_word?(step[:keyword]) && !but_word?(step[:keyword]) }
8
+ steps = scenario.steps.select { |step| !and_word?(step.keyword) && !but_word?(step.keyword) }
7
9
  next if steps.empty?
8
10
 
9
11
  last_step_is_an_action(feature, scenario, steps)
@@ -13,7 +15,7 @@ module Chutney
13
15
  end
14
16
 
15
17
  def last_step_is_an_action(feature, scenario, steps)
16
- return unless when_word?(steps.last[:keyword])
18
+ return unless when_word?(steps.last.keyword)
17
19
 
18
20
  add_issue(I18n.t('linters.invalid_step_flow.action_last'), feature, scenario, steps.last)
19
21
  end
@@ -21,7 +23,7 @@ module Chutney
21
23
  def given_after_non_given(feature, scenario, steps)
22
24
  last_step = steps.first
23
25
  steps.each do |step|
24
- if given_word?(step[:keyword]) && !given_word?(last_step[:keyword])
26
+ if given_word?(step.keyword) && !given_word?(last_step.keyword)
25
27
  add_issue(I18n.t('linters.invalid_step_flow.given_order'), feature, scenario, step)
26
28
  end
27
29
  last_step = step
@@ -30,11 +32,9 @@ module Chutney
30
32
 
31
33
  def verification_before_action(feature, scenario, steps)
32
34
  steps.each do |step|
33
- break if when_word?(step[:keyword])
35
+ break if when_word?(step.keyword)
34
36
 
35
- if then_word?(step[:keyword])
36
- add_issue(I18n.t('linters.invalid_step_flow.missing_action'), feature, scenario)
37
- end
37
+ add_issue(I18n.t('linters.invalid_step_flow.missing_action'), feature, scenario) if then_word?(step.keyword)
38
38
  end
39
39
  end
40
40
  end
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for missing example names
3
5
  class MissingExampleName < Linter
4
6
 
5
7
  def lint
6
8
  scenarios do |_feature, scenario|
7
- next unless scenario[:examples]
9
+ next unless scenario.is_a? CukeModeler::Outline
8
10
 
9
- scenario[:examples].each do |example|
10
- example_count = scenario[:examples]&.length || 0
11
+ scenario.examples.each do |example|
12
+ example_count = scenario.examples&.length || 0
11
13
  next unless example_count > 1
12
14
 
13
15
  check_example(scenario, example)
@@ -16,8 +18,8 @@ module Chutney
16
18
  end
17
19
 
18
20
  def check_example(scenario, example)
19
- name = example.key?(:name) ? example[:name].strip : ''
20
- duplicate_name_count = scenario[:examples].filter { |e| e[:name] == name }.count
21
+ name = example.name.strip
22
+ duplicate_name_count = scenario.examples.filter { |e| e.name == name }.count
21
23
  add_issue(I18n.t('linters.missing_example_name'), feature, scenario, example) if duplicate_name_count >= 2
22
24
  end
23
25
 
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for missing feature descriptions
3
5
  class MissingFeatureDescription < Linter
4
- MESSAGE = 'Features should have a description so that its purpose is clear'.freeze
6
+ MESSAGE = 'Features should have a description so that its purpose is clear'
5
7
  def lint
6
8
  return unless feature
7
9
 
8
- name = feature.key?(:description) ? feature[:description].strip : ''
9
- add_issue(I18n.t('linters.missing_feature_description'), feature) if name.empty?
10
+ add_issue(I18n.t('linters.missing_feature_description'), feature) if feature.description.empty?
10
11
  end
11
12
  end
12
13
  end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for missing feature names
3
5
  class MissingFeatureName < Linter
4
6
  def lint
5
7
  return unless feature
6
8
 
7
- name = feature.key?(:name) ? feature[:name].strip : ''
8
- add_issue(I18n.t('linters.missing_feature_name'), feature) if name.empty?
9
+ add_issue(I18n.t('linters.missing_feature_name'), feature) if feature.name.empty?
9
10
  end
10
11
  end
11
12
  end
@@ -1,13 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for missing scenario names
3
5
  class MissingScenarioName < Linter
4
6
 
5
7
  def lint
6
8
  scenarios do |feature, scenario|
7
- name = scenario.key?(:name) ? scenario[:name].strip : ''
8
- next unless name.empty?
9
-
10
- add_issue(I18n.t('linters.missing_scenario_name'), feature, scenario) if name.empty?
9
+ add_issue(I18n.t('linters.missing_scenario_name'), feature, scenario) if scenario.name.empty?
11
10
  end
12
11
  end
13
12
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for missing test actions
3
5
  class MissingTestAction < Linter
4
6
  def lint
5
7
  filled_scenarios do |feature, scenario|
6
- when_steps = scenario[:steps].select { |step| when_word?(step[:keyword]) }
8
+ when_steps = scenario.steps.select { |step| when_word?(step.keyword) }
7
9
  next unless when_steps.empty?
8
10
 
9
11
  add_issue(I18n.t('linters.missing_test_action'), feature, scenario)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
@@ -5,7 +7,7 @@ module Chutney
5
7
  class MissingVerification < Linter
6
8
  def lint
7
9
  filled_scenarios do |feature, scenario|
8
- then_steps = scenario[:steps].select { |step| then_word?(step[:keyword]) }
10
+ then_steps = scenario.steps.select { |step| then_word?(step.keyword) }
9
11
  next unless then_steps.empty?
10
12
 
11
13
  add_issue(I18n.t('linters.missing_test_verification'), feature, scenario)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for tags used multiple times
3
5
  class RequiredTagsStartsWith < Linter
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
4
6
  # service class to lint for using same tag on all scenarios
5
7
  class SameTagForAllScenarios < Linter
6
8
  def lint
7
- lint_scenarios if feature&.include?(:children)
8
- lint_examples if feature&.include?(:children)
9
+ lint_scenarios if feature&.scenarios
10
+ lint_examples if feature&.scenarios
9
11
  end
10
12
 
11
13
  def lint_scenarios
12
14
  tags = scenario_tags
13
15
  return if tags.nil? || tags.empty?
14
- return unless feature[:children].length > 1
16
+ return unless feature.tests.length > 1
15
17
 
16
18
  tags.each do |tag|
17
19
  next if tag == 'skip'
@@ -25,10 +27,11 @@ module Chutney
25
27
  end
26
28
 
27
29
  def lint_examples
28
- feature[:children].each do |scenario|
30
+ scenarios do |_feature, scenario|
29
31
  tags = example_tags(scenario)
30
32
  next if tags.nil? || tags.empty?
31
- next unless scenario[:examples].length > 1
33
+ next unless scenario.is_a? CukeModeler::Outline
34
+ next unless scenario.examples.length > 1
32
35
 
33
36
  tags.each do |tag|
34
37
  next if tag == 'skip'
@@ -42,8 +45,6 @@ module Chutney
42
45
  def scenario_tags
43
46
  result = nil
44
47
  scenarios do |_feature, scenario|
45
- next if scenario[:type] == :Background
46
-
47
48
  tags = tags_for(scenario)
48
49
  result ||= tags
49
50
  result &= tags
@@ -53,10 +54,10 @@ module Chutney
53
54
 
54
55
  def example_tags(scenario)
55
56
  result = nil
56
- return result unless scenario.include? :examples
57
+ return result unless scenario.is_a?(CukeModeler::Outline) && scenario.examples
57
58
 
58
- scenario[:examples].each do |example|
59
- return nil unless example.include? :tags
59
+ scenario.examples.each do |example|
60
+ return nil unless example.tags
60
61
 
61
62
  tags = tags_for(example)
62
63
  result = tags if result.nil?
@@ -1,15 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
4
6
  # service class to lint for tags used multiple times
5
7
  class ScenarioNamesMatch < Linter
6
- MESSAGE = 'Scenario Name does not match pattern'.freeze
8
+ MESSAGE = 'Scenario Name does not match pattern'
7
9
 
8
10
 
9
11
  def lint
10
12
  scenarios do |feature, scenario|
11
- name = scenario.key?(:name) ? scenario[:name].strip : ''
12
- next unless (name =~ /#{configuration['Matcher']}/).nil?
13
+ next unless (scenario.name =~ /#{configuration['Matcher']}/).nil?
13
14
 
14
15
  add_issue(
15
16
  I18n.t('linters.scenario_names_match',
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # service class to lint for tags used multiple times
3
5
  class TagUsedMultipleTimes < Linter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chutney/linter'
2
4
 
3
5
  module Chutney
@@ -5,7 +7,7 @@ module Chutney
5
7
  class TooClumsy < Linter
6
8
  def lint
7
9
  filled_scenarios do |feature, scenario|
8
- characters = scenario[:steps].map { |step| step[:text].length }.inject(0, :+)
10
+ characters = scenario.steps.map { |step| step.text.length }.inject(0, :+)
9
11
  next if characters < 400
10
12
 
11
13
  add_issue(