chutney 0.5.0
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 +7 -0
- data/.circleci/config.yml +14 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +55 -0
- data/Dockerfile +9 -0
- data/Gemfile +3 -0
- data/Guardfile +3 -0
- data/LICENSE +22 -0
- data/README.md +84 -0
- data/Rakefile +51 -0
- data/chutney.gemspec +54 -0
- data/config/default.yml +58 -0
- data/exe/chutney +35 -0
- data/lib/chutney/.DS_Store +0 -0
- data/lib/chutney/configuration.rb +32 -0
- data/lib/chutney/issue.rb +35 -0
- data/lib/chutney/linter/avoid_outline_for_single_example.rb +19 -0
- data/lib/chutney/linter/avoid_period.rb +19 -0
- data/lib/chutney/linter/avoid_scripting.rb +24 -0
- data/lib/chutney/linter/background_does_more_than_setup.rb +20 -0
- data/lib/chutney/linter/background_requires_multiple_scenarios.rb +18 -0
- data/lib/chutney/linter/bad_scenario_name.rb +21 -0
- data/lib/chutney/linter/be_declarative.rb +49 -0
- data/lib/chutney/linter/file_name_differs_feature_name.rb +27 -0
- data/lib/chutney/linter/invalid_file_name.rb +16 -0
- data/lib/chutney/linter/invalid_step_flow.rb +41 -0
- data/lib/chutney/linter/missing_example_name.rb +23 -0
- data/lib/chutney/linter/missing_feature_description.rb +17 -0
- data/lib/chutney/linter/missing_feature_name.rb +18 -0
- data/lib/chutney/linter/missing_scenario_name.rb +18 -0
- data/lib/chutney/linter/missing_test_action.rb +16 -0
- data/lib/chutney/linter/missing_verification.rb +16 -0
- data/lib/chutney/linter/required_tags_starts_with.rb +16 -0
- data/lib/chutney/linter/same_tag_for_all_scenarios.rb +73 -0
- data/lib/chutney/linter/tag_collector.rb +10 -0
- data/lib/chutney/linter/tag_constraint.rb +35 -0
- data/lib/chutney/linter/tag_used_multiple_times.rb +23 -0
- data/lib/chutney/linter/too_clumsy.rb +17 -0
- data/lib/chutney/linter/too_long_step.rb +17 -0
- data/lib/chutney/linter/too_many_different_tags.rb +45 -0
- data/lib/chutney/linter/too_many_steps.rb +15 -0
- data/lib/chutney/linter/too_many_tags.rb +19 -0
- data/lib/chutney/linter/unique_scenario_names.rb +22 -0
- data/lib/chutney/linter/unknown_variable.rb +47 -0
- data/lib/chutney/linter/unused_variable.rb +47 -0
- data/lib/chutney/linter/use_background.rb +82 -0
- data/lib/chutney/linter/use_outline.rb +53 -0
- data/lib/chutney/linter.rb +164 -0
- data/lib/chutney/version.rb +3 -0
- data/lib/chutney.rb +131 -0
- data/spec/chutney_spec.rb +68 -0
- data/spec/configuration_spec.rb +58 -0
- data/spec/required_tags_starts_with_spec.rb +74 -0
- data/spec/shared_contexts/file_exists.rb +12 -0
- data/spec/shared_contexts/gherkin_linter.rb +14 -0
- metadata +201 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'amatch'
|
2
|
+
require 'chutney/linter'
|
3
|
+
|
4
|
+
module Chutney
|
5
|
+
# service class to lint for using outline
|
6
|
+
class UseOutline < Linter
|
7
|
+
def lint
|
8
|
+
features do |file, feature|
|
9
|
+
check_similarity gather_scenarios(file, feature)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_similarity(scenarios)
|
14
|
+
scenarios.product(scenarios) do |lhs, rhs|
|
15
|
+
next if lhs == rhs
|
16
|
+
next if lhs[:reference] > rhs[:reference]
|
17
|
+
|
18
|
+
similarity = determine_similarity(lhs[:text], rhs[:text])
|
19
|
+
next unless similarity >= 0.95
|
20
|
+
|
21
|
+
similarity_pct = similarity.round(3) * 100
|
22
|
+
references = [lhs[:reference], rhs[:reference]]
|
23
|
+
add_error(references, "Scenarios are similar by #{similarity_pct} %, use Background steps to simplify")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def determine_similarity(lhs, rhs)
|
28
|
+
matcher = Amatch::Jaro.new lhs
|
29
|
+
matcher.match rhs
|
30
|
+
end
|
31
|
+
|
32
|
+
def gather_scenarios(file, feature)
|
33
|
+
scenarios = []
|
34
|
+
return scenarios unless feature.include? :children
|
35
|
+
|
36
|
+
feature[:children].each do |scenario|
|
37
|
+
next unless scenario[:type] == :Scenario
|
38
|
+
next unless scenario.include? :steps
|
39
|
+
next if scenario[:steps].empty?
|
40
|
+
|
41
|
+
scenarios.push generate_reference(file, feature, scenario)
|
42
|
+
end
|
43
|
+
scenarios
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_reference(file, feature, scenario)
|
47
|
+
reference = {}
|
48
|
+
reference[:reference] = reference(file, feature, scenario)
|
49
|
+
reference[:text] = scenario[:steps].map { |step| render_step(step) }.join ' '
|
50
|
+
reference
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'chutney/issue'
|
2
|
+
|
3
|
+
# gherkin utilities
|
4
|
+
module Chutney
|
5
|
+
# base class for all linters
|
6
|
+
class Linter
|
7
|
+
attr_reader :issues
|
8
|
+
|
9
|
+
def self.descendants
|
10
|
+
ObjectSpace.each_object(::Class).select { |klass| klass < self }
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@issues = []
|
15
|
+
@files = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def features
|
19
|
+
@files.each do |file, content|
|
20
|
+
feature = content[:feature]
|
21
|
+
next if feature.nil?
|
22
|
+
|
23
|
+
yield(file, feature)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def files
|
28
|
+
@files.each_key { |file| yield file }
|
29
|
+
end
|
30
|
+
|
31
|
+
def scenarios
|
32
|
+
elements do |file, feature, scenario|
|
33
|
+
next if scenario[:type] == :Background
|
34
|
+
|
35
|
+
yield(file, feature, scenario)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def filled_scenarios
|
40
|
+
scenarios do |file, feature, scenario|
|
41
|
+
next unless scenario.include? :steps
|
42
|
+
next if scenario[:steps].empty?
|
43
|
+
|
44
|
+
yield(file, feature, scenario)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def steps
|
49
|
+
elements do |file, feature, scenario|
|
50
|
+
next unless scenario.include? :steps
|
51
|
+
|
52
|
+
scenario[:steps].each { |step| yield(file, feature, scenario, step) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def backgrounds
|
57
|
+
elements do |file, feature, scenario|
|
58
|
+
next unless scenario[:type] == :Background
|
59
|
+
|
60
|
+
yield(file, feature, scenario)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def elements
|
65
|
+
@files.each do |file, content|
|
66
|
+
feature = content[:feature]
|
67
|
+
next if feature.nil?
|
68
|
+
next unless feature.key? :children
|
69
|
+
|
70
|
+
feature[:children].each do |scenario|
|
71
|
+
yield(file, feature, scenario)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def name
|
77
|
+
self.class.name.split('::').last
|
78
|
+
end
|
79
|
+
|
80
|
+
def lint_files(files, tags_to_suppress)
|
81
|
+
@files = files
|
82
|
+
@files = filter_tag(@files, "disable#{name}")
|
83
|
+
@files = suppress_tags(@files, tags_to_suppress)
|
84
|
+
lint
|
85
|
+
end
|
86
|
+
|
87
|
+
def filter_tag(data, tag)
|
88
|
+
return data.reject { |item| tag?(item, tag) }.map { |item| filter_tag(item, tag) } if data.class == Array
|
89
|
+
return {} if (data.class == Hash) && (data.include? :feature) && tag?(data[:feature], tag)
|
90
|
+
return data unless data.respond_to? :each_pair
|
91
|
+
|
92
|
+
result = {}
|
93
|
+
data.each_pair { |key, value| result[key] = filter_tag(value, tag) }
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def tag?(data, tag)
|
98
|
+
return false if data.class != Hash
|
99
|
+
return false unless data.include? :tags
|
100
|
+
|
101
|
+
data[:tags].map { |item| item[:name] }.include? "@#{tag}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def suppress_tags(data, tags)
|
105
|
+
return data.map { |item| suppress_tags(item, tags) } if data.class == Array
|
106
|
+
return data unless data.class == Hash
|
107
|
+
|
108
|
+
result = {}
|
109
|
+
|
110
|
+
data.each_pair do |key, value|
|
111
|
+
value = suppress(value, tags) if key == :tags
|
112
|
+
result[key] = suppress_tags(value, tags)
|
113
|
+
end
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
def suppress(data, tags)
|
118
|
+
data.reject { |item| tags.map { |tag| "@#{tag}" }.include? item[:name] }
|
119
|
+
end
|
120
|
+
|
121
|
+
def lint
|
122
|
+
raise 'not implemented'
|
123
|
+
end
|
124
|
+
|
125
|
+
def reference(file, feature = nil, scenario = nil, step = nil)
|
126
|
+
return file if feature.nil? || feature[:name].empty?
|
127
|
+
|
128
|
+
result = "#{file} (#{line(feature, scenario, step)}): #{feature[:name]}"
|
129
|
+
result += ".#{scenario[:name]}" unless scenario.nil? || scenario[:name].empty?
|
130
|
+
result += " step: #{step[:text]}" unless step.nil?
|
131
|
+
result
|
132
|
+
end
|
133
|
+
|
134
|
+
def line(feature, scenario, step)
|
135
|
+
line = feature.nil? ? nil : feature[:location][:line]
|
136
|
+
line = scenario[:location][:line] unless scenario.nil?
|
137
|
+
line = step[:location][:line] unless step.nil?
|
138
|
+
line
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_error(references, description = nil)
|
142
|
+
@issues.push Error.new(name, references, description)
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_warning(references, description = nil)
|
146
|
+
@issues.push Warning.new(name, references, description)
|
147
|
+
end
|
148
|
+
|
149
|
+
def render_step(step)
|
150
|
+
value = "#{step[:keyword]}#{step[:text]}"
|
151
|
+
value += render_step_argument step[:argument] if step.include? :argument
|
152
|
+
value
|
153
|
+
end
|
154
|
+
|
155
|
+
def render_step_argument(argument)
|
156
|
+
return "\n#{argument[:content]}" if argument[:type] == :DocString
|
157
|
+
|
158
|
+
result = argument[:rows].map do |row|
|
159
|
+
"|#{row[:cells].map { |cell| cell[:value] }.join '|'}|"
|
160
|
+
end.join "\n"
|
161
|
+
"\n#{result}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/lib/chutney.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'gherkin/parser'
|
2
|
+
require 'chutney/linter'
|
3
|
+
require 'chutney/linter/avoid_outline_for_single_example'
|
4
|
+
require 'chutney/linter/avoid_period'
|
5
|
+
require 'chutney/linter/avoid_scripting'
|
6
|
+
require 'chutney/linter/background_does_more_than_setup'
|
7
|
+
require 'chutney/linter/background_requires_multiple_scenarios'
|
8
|
+
require 'chutney/linter/bad_scenario_name'
|
9
|
+
require 'chutney/linter/be_declarative'
|
10
|
+
require 'chutney/linter/file_name_differs_feature_name'
|
11
|
+
require 'chutney/linter/invalid_file_name'
|
12
|
+
require 'chutney/linter/invalid_step_flow'
|
13
|
+
require 'chutney/linter/missing_example_name'
|
14
|
+
require 'chutney/linter/missing_feature_description'
|
15
|
+
require 'chutney/linter/missing_feature_name'
|
16
|
+
require 'chutney/linter/missing_scenario_name'
|
17
|
+
require 'chutney/linter/missing_test_action'
|
18
|
+
require 'chutney/linter/missing_verification'
|
19
|
+
require 'chutney/linter/required_tags_starts_with'
|
20
|
+
require 'chutney/linter/same_tag_for_all_scenarios'
|
21
|
+
require 'chutney/linter/tag_used_multiple_times'
|
22
|
+
require 'chutney/linter/too_clumsy'
|
23
|
+
require 'chutney/linter/too_long_step'
|
24
|
+
require 'chutney/linter/too_many_different_tags'
|
25
|
+
require 'chutney/linter/too_many_steps'
|
26
|
+
require 'chutney/linter/too_many_tags'
|
27
|
+
require 'chutney/linter/unique_scenario_names'
|
28
|
+
require 'chutney/linter/unknown_variable'
|
29
|
+
require 'chutney/linter/unused_variable'
|
30
|
+
require 'chutney/linter/use_background'
|
31
|
+
require 'chutney/linter/use_outline'
|
32
|
+
require 'multi_json'
|
33
|
+
require 'set'
|
34
|
+
require 'chutney/configuration'
|
35
|
+
|
36
|
+
module Chutney
|
37
|
+
# gherkin linter
|
38
|
+
class ChutneyLint
|
39
|
+
attr_accessor :verbose
|
40
|
+
default_file = File.expand_path('..', __dir__), '**/config', 'default.yml'
|
41
|
+
DEFAULT_CONFIG = Dir.glob(File.join(default_file)).first.freeze
|
42
|
+
LINTER = Linter.descendants
|
43
|
+
|
44
|
+
def initialize(path = nil)
|
45
|
+
@files = {}
|
46
|
+
@linter = []
|
47
|
+
@config = Configuration.new path || DEFAULT_CONFIG
|
48
|
+
@verbose = false
|
49
|
+
end
|
50
|
+
|
51
|
+
def enabled(linter_name, value)
|
52
|
+
@config.config[linter_name]['Enabled'] = value if @config.config.key? linter_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def enable(enabled_linters)
|
56
|
+
enabled_linters.each do |linter|
|
57
|
+
enabled linter, true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def disable(disabled_linters)
|
62
|
+
disabled_linters.each do |linter|
|
63
|
+
enabled linter, false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Testing feature
|
68
|
+
def disable_all
|
69
|
+
@config.config.each do |member|
|
70
|
+
@config.config[member[0]]['Enabled'] = false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_linter
|
75
|
+
@linter = []
|
76
|
+
LINTER.each do |linter|
|
77
|
+
new_linter = linter.new
|
78
|
+
linter_enabled = @config.config[new_linter.class.name.split('::').last]['Enabled']
|
79
|
+
evaluate_members(new_linter) if linter_enabled
|
80
|
+
@linter.push new_linter if linter_enabled
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def evaluate_members(linter)
|
85
|
+
@config.config[linter.class.name.split('::').last].each do |member, value|
|
86
|
+
next if member.downcase.casecmp('enabled').zero?
|
87
|
+
|
88
|
+
member = member.downcase.to_sym
|
89
|
+
raise 'Member not found! Check the YAML' unless linter.respond_to? member
|
90
|
+
|
91
|
+
linter.public_send(member, value)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def analyze(file)
|
96
|
+
@files[file] = parse file
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse(file)
|
100
|
+
to_json File.read(file)
|
101
|
+
end
|
102
|
+
|
103
|
+
def report
|
104
|
+
issues = @linter.map do |linter|
|
105
|
+
linter.lint_files(@files, disable_tags)
|
106
|
+
linter.issues
|
107
|
+
end.flatten
|
108
|
+
|
109
|
+
print issues
|
110
|
+
return 0 if issues.select { |issue| issue.class == Error }.empty?
|
111
|
+
|
112
|
+
-1
|
113
|
+
end
|
114
|
+
|
115
|
+
def disable_tags
|
116
|
+
LINTER.map { |lint| "disable#{lint.new.class.name.split('::').last}" }
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_json(input)
|
120
|
+
parser = Gherkin::Parser.new
|
121
|
+
scanner = Gherkin::TokenScanner.new input
|
122
|
+
|
123
|
+
parser.parse(scanner)
|
124
|
+
end
|
125
|
+
|
126
|
+
def print(issues)
|
127
|
+
puts 'There are no issues' if issues.empty? && @verbose
|
128
|
+
issues.each { |issue| puts issue.render }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'chutney'
|
3
|
+
require 'chutney/linter/tag_constraint'
|
4
|
+
require 'shared_contexts/file_exists'
|
5
|
+
|
6
|
+
describe Chutney::ChutneyLint do
|
7
|
+
it 'should have the constant set' do
|
8
|
+
expect(Chutney::ChutneyLint.const_defined?(:LINTER)).to be true
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { Chutney::ChutneyLint.new }
|
12
|
+
|
13
|
+
describe '#initialize' do
|
14
|
+
it 'sets the files instance variable to empty' do
|
15
|
+
expect(subject.instance_variable_get(:@files)).to eq({})
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets the linter instance variable to empty' do
|
19
|
+
expect(subject.instance_variable_get(:@linter).size).to eq(0)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#enable' do
|
24
|
+
it 'enables the linter passed in' do
|
25
|
+
subject.enable ['RequiredTagsStartsWith']
|
26
|
+
expect(subject.instance_variable_get(:@config).config).to include('RequiredTagsStartsWith' => { 'Enabled' => true })
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when user configuration is not present' do
|
31
|
+
let(:file) { 'config/default.yml' }
|
32
|
+
it 'should load the expected values from the config file' do
|
33
|
+
expect(subject.instance_variable_get(:@config).config).to include('AvoidOutlineForSingleExample' => { 'Enabled' => true })
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when user provided YAML is present' do
|
38
|
+
include_context 'a file exists'
|
39
|
+
let(:file) { '.chutney.yml' }
|
40
|
+
let(:file_content) do
|
41
|
+
<<-CONTENT
|
42
|
+
---
|
43
|
+
AvoidOutlineForSingleExample:
|
44
|
+
Enabled: false
|
45
|
+
CONTENT
|
46
|
+
end
|
47
|
+
it 'should load and merge the expected values from the user config file' do
|
48
|
+
expect(subject.instance_variable_get(:@config).config).to include('AvoidOutlineForSingleExample' => { 'Enabled' => false })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when linter member value is passed by the user' do
|
53
|
+
include_context 'a file exists'
|
54
|
+
let(:file) { '.chutney.yml' }
|
55
|
+
let(:file_content) do
|
56
|
+
<<-CONTENT
|
57
|
+
---
|
58
|
+
RequiredTags:
|
59
|
+
Enabled: true
|
60
|
+
Member: Value
|
61
|
+
CONTENT
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'updates the member in the config' do
|
65
|
+
expect(subject.instance_variable_get(:@config).config).to include('RequiredTags' => { 'Enabled' => true, 'Member' => 'Value' })
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'chutney/configuration'
|
3
|
+
require 'shared_contexts/file_exists'
|
4
|
+
|
5
|
+
describe Chutney::Configuration do
|
6
|
+
subject { Chutney::Configuration.new file }
|
7
|
+
let(:file) { 'default.yml' }
|
8
|
+
|
9
|
+
it 'should do something' do
|
10
|
+
expect(subject.config).to eq('')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have a default config path' do
|
14
|
+
expect(subject.configuration_path).not_to be nil
|
15
|
+
end
|
16
|
+
context 'when a empty config file is present' do
|
17
|
+
include_context 'a file exists'
|
18
|
+
let(:file_content) { '---' }
|
19
|
+
it 'should load a file from the config path' do
|
20
|
+
expect(subject.config).to eq ''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when a non-YAML config file is present' do
|
25
|
+
include_context 'a file exists'
|
26
|
+
let(:file_content) do
|
27
|
+
<<-CONTENT
|
28
|
+
foo: [
|
29
|
+
‘bar’, {
|
30
|
+
baz: 42
|
31
|
+
}
|
32
|
+
]'
|
33
|
+
CONTENT
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should load a file from the config path but fail to parse' do
|
37
|
+
expect { subject.load_configuration }.to raise_error
|
38
|
+
expect { subject.config }.to raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
context 'when a valid YAML file is present' do
|
42
|
+
include_context 'a file exists'
|
43
|
+
let(:file_content) do
|
44
|
+
<<-CONTENT
|
45
|
+
---
|
46
|
+
:parent_key: parent_value
|
47
|
+
:child_key: child_value
|
48
|
+
CONTENT
|
49
|
+
end
|
50
|
+
before :each do
|
51
|
+
subject.load_configuration
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should load the values from the config file' do
|
55
|
+
expect(subject.config).to eq(parent_key: 'parent_value', child_key: 'child_value')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'chutney/linter/required_tags_starts_with'
|
3
|
+
require 'chutney'
|
4
|
+
require 'shared_contexts/gherkin_linter'
|
5
|
+
|
6
|
+
describe Chutney::RequiredTagsStartsWith do
|
7
|
+
let(:linter) { Chutney::ChutneyLint.new }
|
8
|
+
let(:file) { 'lint.feature' }
|
9
|
+
let(:pattern) { %w[MCC PB] }
|
10
|
+
describe '#matcher' do
|
11
|
+
it 'should raise an error when pattern is nil' do
|
12
|
+
expect { subject.matcher(nil) }.to raise_error('No Tags provided in the YAML')
|
13
|
+
end
|
14
|
+
it 'should raise an error when pattern is empty' do
|
15
|
+
expect { subject.matcher('') }.to output("Required Tags matcher has no value\n").to_stderr
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#issues' do
|
20
|
+
context 'before linting' do
|
21
|
+
it 'should have no issue' do
|
22
|
+
expect(subject.issues.size).to eq(0)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'after linting a feature file with valid PB tag at the feature level' do
|
27
|
+
include_context 'a gherkin linter'
|
28
|
+
|
29
|
+
let(:file_content) do
|
30
|
+
<<-CONTENT
|
31
|
+
@PB
|
32
|
+
Feature: Test
|
33
|
+
@scenario_tag
|
34
|
+
Scenario: A
|
35
|
+
CONTENT
|
36
|
+
end
|
37
|
+
it 'should have no issues' do
|
38
|
+
expect(subject.issues.size).to eq(0)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'after linting a file with a MCC tag at the scenario level' do
|
43
|
+
include_context 'a gherkin linter'
|
44
|
+
let(:file_content) do
|
45
|
+
<<-CONTENT
|
46
|
+
@feature_tag
|
47
|
+
Feature: Test
|
48
|
+
@MCC
|
49
|
+
Scenario: A
|
50
|
+
CONTENT
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should have no issues' do
|
54
|
+
expect(subject.issues.size).to eq(0)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'after linting a file with no required tags' do
|
59
|
+
include_context 'a gherkin linter'
|
60
|
+
let(:file_content) do
|
61
|
+
<<-CONTENT
|
62
|
+
@feature_tag
|
63
|
+
Feature: Test
|
64
|
+
@scenario_tag
|
65
|
+
Scenario: A
|
66
|
+
CONTENT
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should have issues after linting a file without PB or MCC tags' do
|
70
|
+
expect(subject.issues[0].name).to eq(subject.class.name.split('::').last)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative 'file_exists'
|
3
|
+
|
4
|
+
shared_context 'a gherkin linter' do
|
5
|
+
include_context 'a file exists'
|
6
|
+
|
7
|
+
let(:files) { linter.analyze file }
|
8
|
+
let(:disable_tags) { linter.disable_tags }
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
subject.instance_variable_set(:@pattern, pattern)
|
12
|
+
subject.lint_files({ file: files }, disable_tags)
|
13
|
+
end
|
14
|
+
end
|