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.
- 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(
|