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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/Gemfile +2 -0
- data/Rakefile +2 -0
- data/chutney.gemspec +8 -4
- data/config/cucumber.yml +1 -0
- data/exe/chutney +2 -0
- data/lib/chutney.rb +9 -11
- data/lib/chutney/configuration.rb +2 -0
- data/lib/chutney/formatter.rb +2 -0
- data/lib/chutney/formatter/json_formatter.rb +2 -0
- data/lib/chutney/formatter/pie_formatter.rb +2 -0
- data/lib/chutney/formatter/rainbow_formatter.rb +2 -0
- data/lib/chutney/issue.rb +2 -0
- data/lib/chutney/linter.rb +58 -59
- data/lib/chutney/linter/avoid_full_stop.rb +3 -1
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +8 -6
- data/lib/chutney/linter/avoid_scripting.rb +6 -4
- data/lib/chutney/linter/avoid_typographers_quotes.rb +16 -14
- data/lib/chutney/linter/background_does_more_than_setup.rb +8 -6
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +6 -3
- data/lib/chutney/linter/bad_scenario_name.rb +4 -2
- data/lib/chutney/linter/empty_feature_file.rb +2 -0
- data/lib/chutney/linter/file_name_differs_feature_name.rb +4 -2
- data/lib/chutney/linter/givens_after_background.rb +5 -6
- data/lib/chutney/linter/invalid_file_name.rb +2 -0
- data/lib/chutney/linter/invalid_step_flow.rb +7 -7
- data/lib/chutney/linter/missing_example_name.rb +7 -5
- data/lib/chutney/linter/missing_feature_description.rb +4 -3
- data/lib/chutney/linter/missing_feature_name.rb +3 -2
- data/lib/chutney/linter/missing_scenario_name.rb +3 -4
- data/lib/chutney/linter/missing_test_action.rb +3 -1
- data/lib/chutney/linter/missing_verification.rb +3 -1
- data/lib/chutney/linter/required_tags_starts_with.rb +2 -0
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +11 -10
- data/lib/chutney/linter/scenario_names_match.rb +4 -3
- data/lib/chutney/linter/tag_used_multiple_times.rb +2 -0
- data/lib/chutney/linter/too_clumsy.rb +3 -1
- data/lib/chutney/linter/too_long_step.rb +4 -2
- data/lib/chutney/linter/too_many_different_tags.rb +4 -2
- data/lib/chutney/linter/too_many_steps.rb +4 -2
- data/lib/chutney/linter/too_many_tags.rb +2 -0
- data/lib/chutney/linter/unique_scenario_names.rb +3 -3
- data/lib/chutney/linter/unknown_variable.rb +13 -13
- data/lib/chutney/linter/unused_variable.rb +13 -13
- data/lib/chutney/linter/use_background.rb +17 -16
- data/lib/chutney/linter/use_outline.rb +8 -7
- data/lib/chutney/version.rb +3 -1
- data/spec/chutney_spec.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- 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
|
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
|
15
|
-
.then { |s| s.reverse.drop_while { |step| !then_word?(step
|
16
|
-
.then { |s| s.reject { |step| then_word?(step
|
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 |
|
8
|
-
lint_steps(scenario
|
9
|
+
scenarios do |feature, scenario|
|
10
|
+
lint_steps(feature, scenario)
|
9
11
|
|
10
|
-
example_count = scenario
|
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
|
15
|
+
lint_examples(feature, scenario)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
def lint_steps(
|
18
|
-
steps.each do |step|
|
19
|
-
issue(step) if TYPOGRAPHER_QUOTES.any? { |tq| step
|
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(
|
24
|
-
examples.each do |example|
|
25
|
-
example
|
26
|
-
|
27
|
-
issue(cell) if TYPOGRAPHER_QUOTES.any? { |tq| cell
|
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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
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'
|
8
|
+
MESSAGE = 'Avoid using Background steps for just one scenario'
|
7
9
|
|
8
10
|
def lint
|
9
11
|
background do |feature, background|
|
10
|
-
|
11
|
-
next
|
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
|
8
|
+
next if scenario.name.empty?
|
7
9
|
|
8
10
|
bad = /\w*(test|verif|check)\w*/i
|
9
|
-
match = scenario
|
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,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
|
7
|
+
return unless feature
|
6
8
|
|
7
9
|
expected_feature_name = title_case(filename)
|
8
|
-
return if ignore_whitespaces(feature
|
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
|
6
|
-
return if background.empty?
|
7
|
+
return unless background
|
7
8
|
|
8
9
|
filled_scenarios do |feature, scenario|
|
9
|
-
scenario
|
10
|
-
if given_word?(step
|
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,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
|
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
|
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
|
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
|
35
|
+
break if when_word?(step.keyword)
|
34
36
|
|
35
|
-
if then_word?(step
|
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
|
9
|
+
next unless scenario.is_a? CukeModeler::Outline
|
8
10
|
|
9
|
-
scenario
|
10
|
-
example_count = scenario
|
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.
|
20
|
-
duplicate_name_count = scenario
|
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'
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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,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&.
|
8
|
-
lint_examples if feature&.
|
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
|
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
|
-
|
30
|
+
scenarios do |_feature, scenario|
|
29
31
|
tags = example_tags(scenario)
|
30
32
|
next if tags.nil? || tags.empty?
|
31
|
-
next unless scenario
|
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.
|
57
|
+
return result unless scenario.is_a?(CukeModeler::Outline) && scenario.examples
|
57
58
|
|
58
|
-
scenario
|
59
|
-
return nil unless example.
|
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'
|
8
|
+
MESSAGE = 'Scenario Name does not match pattern'
|
7
9
|
|
8
10
|
|
9
11
|
def lint
|
10
12
|
scenarios do |feature, scenario|
|
11
|
-
|
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
|
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
|
10
|
+
characters = scenario.steps.map { |step| step.text.length }.inject(0, :+)
|
9
11
|
next if characters < 400
|
10
12
|
|
11
13
|
add_issue(
|