chutney 2.2.1 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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(