rubocop-rspec 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,49 +2,37 @@ describe RuboCop::Cop::RSpec::InstanceVariable do
2
2
  subject(:cop) { described_class.new }
3
3
 
4
4
  it 'finds an instance variable inside a describe' do
5
- inspect_source(
6
- cop,
7
- [
8
- 'describe MyClass do',
9
- ' before { @foo = [] }',
10
- ' it { expect(@foo).to be_empty }',
11
- 'end'
12
- ]
13
- )
14
- expect(cop.offenses.size).to eq(1)
15
- expect(cop.offenses.map(&:line).sort).to eq([3])
16
- expect(cop.messages).to eq(['Use `let` instead of an instance variable'])
5
+ expect_violation(<<-RUBY)
6
+ describe MyClass do
7
+ before { @foo = [] }
8
+ it { expect(@foo).to be_empty }
9
+ ^^^^ Use `let` instead of an instance variable
10
+ end
11
+ RUBY
17
12
  end
18
13
 
19
14
  it 'ignores non-spec blocks' do
20
- inspect_source(
21
- cop,
22
- [
23
- 'not_rspec do',
24
- ' before { @foo = [] }',
25
- ' it { expect(@foo).to be_empty }',
26
- 'end'
27
- ]
28
- )
29
- expect(cop.offenses).to be_empty
15
+ expect_no_violations(<<-RUBY)
16
+ not_rspec do
17
+ before { @foo = [] }
18
+ it { expect(@foo).to be_empty }
19
+ end
20
+ RUBY
30
21
  end
31
22
 
32
23
  it 'finds an instance variable inside a shared example' do
33
- inspect_source(
34
- cop,
35
- [
36
- "shared_examples 'shared example' do",
37
- ' it { expect(@foo).to be_empty }',
38
- 'end'
39
- ]
40
- )
41
- expect(cop.offenses.size).to eq(1)
42
- expect(cop.offenses.map(&:line).sort).to eq([2])
43
- expect(cop.messages).to eq(['Use `let` instead of an instance variable'])
24
+ expect_violation(<<-RUBY)
25
+ shared_examples 'shared example' do
26
+ it { expect(@foo).to be_empty }
27
+ ^^^^ Use `let` instead of an instance variable
28
+ end
29
+ RUBY
44
30
  end
45
31
 
46
32
  it 'ignores an instance variable without describe' do
47
- inspect_source(cop, ['@foo = []', '@foo.empty?'])
48
- expect(cop.offenses).to be_empty
33
+ expect_no_violations(<<-RUBY)
34
+ @foo = []
35
+ @foo.empty?
36
+ RUBY
49
37
  end
50
38
  end
@@ -2,42 +2,27 @@ describe RuboCop::Cop::RSpec::MultipleDescribes do
2
2
  subject(:cop) { described_class.new }
3
3
 
4
4
  it 'finds multiple top level describes with class and method' do
5
- inspect_source(
6
- cop,
7
- [
8
- "describe MyClass, '.do_something' do; end",
9
- "describe MyClass, '.do_something_else' do; end"
10
- ]
11
- )
12
- expect(cop.offenses.size).to eq(1)
13
- expect(cop.offenses.map(&:line).sort).to eq([1])
14
- expect(cop.messages).to eq(['Do not use multiple top level describes - ' \
15
- 'try to nest them.'])
5
+ expect_violation(<<-RUBY)
6
+ describe MyClass, '.do_something' do; end
7
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use multiple top level describes - try to nest them.
8
+ describe MyClass, '.do_something_else' do; end
9
+ RUBY
16
10
  end
17
11
 
18
12
  it 'finds multiple top level describes only with class' do
19
- inspect_source(
20
- cop,
21
- [
22
- 'describe MyClass do; end',
23
- 'describe MyOtherClass do; end'
24
- ]
25
- )
26
- expect(cop.offenses.size).to eq(1)
27
- expect(cop.offenses.map(&:line).sort).to eq([1])
28
- expect(cop.messages).to eq(['Do not use multiple top level describes - ' \
29
- 'try to nest them.'])
13
+ expect_violation(<<-RUBY)
14
+ describe MyClass do; end
15
+ ^^^^^^^^^^^^^^^^ Do not use multiple top level describes - try to nest them.
16
+ describe MyOtherClass do; end
17
+ RUBY
30
18
  end
31
19
 
32
20
  it 'skips single top level describe' do
33
- inspect_source(
34
- cop,
35
- [
36
- "require 'spec_helper'",
37
- '',
38
- 'describe MyClass do; end'
39
- ]
40
- )
41
- expect(cop.offenses).to be_empty
21
+ expect_no_violations(<<-RUBY)
22
+ require 'spec_helper'
23
+
24
+ describe MyClass do
25
+ end
26
+ RUBY
42
27
  end
43
28
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RuboCop::Cop::RSpec::NamedSubject do
4
+ subject(:cop) { described_class.new }
5
+
6
+ it 'checks `it` and `specify` for explicit subject usage' do
7
+ expect_violation(<<-RUBY)
8
+ RSpec.describe User do
9
+ subject { described_class.new }
10
+
11
+ it "is valid" do
12
+ expect(subject.valid?).to be(true)
13
+ ^^^^^^^ Name your test subject if you need to reference it explicitly.
14
+ end
15
+
16
+ specify do
17
+ expect(subject.valid?).to be(true)
18
+ ^^^^^^^ Name your test subject if you need to reference it explicitly.
19
+ end
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ it 'checks before and after for explicit subject usage' do
25
+ expect_violation(<<-RUBY)
26
+ RSpec.describe User do
27
+ subject { described_class.new }
28
+
29
+ before(:each) do
30
+ do_something_with(subject)
31
+ ^^^^^^^ Name your test subject if you need to reference it explicitly.
32
+ end
33
+
34
+ after do
35
+ do_something_with(subject)
36
+ ^^^^^^^ Name your test subject if you need to reference it explicitly.
37
+ end
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ it 'checks around(:each) for explicit subject usage' do
43
+ expect_violation(<<-RUBY)
44
+ RSpec.describe User do
45
+ subject { described_class.new }
46
+
47
+ around(:each) do |test|
48
+ do_something_with(subject)
49
+ ^^^^^^^ Name your test subject if you need to reference it explicitly.
50
+ end
51
+ end
52
+ RUBY
53
+ end
54
+
55
+ it 'ignores subject when not wrapped inside a test' do
56
+ expect_no_violations(<<-RUBY)
57
+ def foo
58
+ it(subject)
59
+ end
60
+ RUBY
61
+ end
62
+ end
@@ -5,17 +5,16 @@ describe RuboCop::Cop::RSpec::NotToNot, :config do
5
5
  let(:cop_config) { { 'EnforcedStyle' => 'not_to' } }
6
6
 
7
7
  it 'detects the `to_not` offense' do
8
- inspect_source(subject, 'it { expect(false).to_not be_true }')
9
-
10
- expect(subject.messages).to eq(['Prefer `not_to` over `to_not`'])
11
- expect(subject.highlights).to eq(['expect(false).to_not be_true'])
12
- expect(subject.offenses.map(&:line).sort).to eq([1])
8
+ expect_violation(<<-RUBY)
9
+ it { expect(false).to_not be_true }
10
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `not_to` over `to_not`
11
+ RUBY
13
12
  end
14
13
 
15
14
  it 'detects no offense when using `not_to`' do
16
- inspect_source(subject, 'it { expect(false).not_to be_true }')
17
-
18
- expect(subject.messages).to be_empty
15
+ expect_no_violations(<<-RUBY)
16
+ it { expect(false).not_to be_true }
17
+ RUBY
19
18
  end
20
19
 
21
20
  it 'auto-corrects `to_not` to `not_to`' do
@@ -28,17 +27,16 @@ describe RuboCop::Cop::RSpec::NotToNot, :config do
28
27
  let(:cop_config) { { 'EnforcedStyle' => 'to_not' } }
29
28
 
30
29
  it 'detects the `not_to` offense' do
31
- inspect_source(subject, 'it { expect(false).not_to be_true }')
32
-
33
- expect(subject.messages).to eq(['Prefer `to_not` over `not_to`'])
34
- expect(subject.highlights).to eq(['expect(false).not_to be_true'])
35
- expect(subject.offenses.map(&:line).sort).to eq([1])
30
+ expect_violation(<<-RUBY)
31
+ it { expect(false).not_to be_true }
32
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `to_not` over `not_to`
33
+ RUBY
36
34
  end
37
35
 
38
36
  it 'detects no offense when using `to_not`' do
39
- inspect_source(subject, 'it { expect(false).to_not be_true }')
40
-
41
- expect(subject.messages).to be_empty
37
+ expect_no_violations(<<-RUBY)
38
+ it { expect(false).to_not be_true }
39
+ RUBY
42
40
  end
43
41
 
44
42
  it 'auto-corrects `not_to` to `to_not`' do
@@ -2,39 +2,33 @@ describe RuboCop::Cop::RSpec::VerifiedDoubles, :config do
2
2
  subject(:cop) { described_class.new(config) }
3
3
 
4
4
  it 'finds a `double` instead of an `instance_double`' do
5
- inspect_source(cop, ['it do',
6
- ' foo = double("Widget")',
7
- 'end'])
8
- expect(cop.messages)
9
- .to eq(['Prefer using verifying doubles over normal doubles.'])
10
- expect(cop.highlights).to eq(['double("Widget")'])
11
- expect(cop.offenses.map(&:line).sort).to eq([2])
12
- expect(cop.offenses.map(&:to_s).sort).to all(
13
- eql('C: 2: 9: Prefer using verifying doubles over normal doubles.')
14
- )
5
+ expect_violation(<<-RUBY)
6
+ it do
7
+ foo = double("Widget")
8
+ ^^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
9
+ end
10
+ RUBY
15
11
  end
16
12
 
17
13
  context 'when configuration does not specify IgnoreSymbolicNames' do
18
14
  let(:cop_config) { Hash.new }
19
15
 
20
16
  it 'find doubles whose name is a symbol' do
21
- inspect_source(cop, ['it do',
22
- ' foo = double(:widget)',
23
- 'end'])
24
- expect(cop.messages)
25
- .to eq(['Prefer using verifying doubles over normal doubles.'])
26
- expect(cop.highlights).to eq(['double(:widget)'])
27
- expect(cop.offenses.map(&:line).sort).to eq([2])
17
+ expect_violation(<<-RUBY)
18
+ it do
19
+ foo = double(:widget)
20
+ ^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
21
+ end
22
+ RUBY
28
23
  end
29
24
 
30
25
  it 'finds a `spy` instead of an `instance_spy`' do
31
- inspect_source(cop, ['it do',
32
- ' foo = spy("Widget")',
33
- 'end'])
34
- expect(cop.messages)
35
- .to eq(['Prefer using verifying doubles over normal doubles.'])
36
- expect(cop.highlights).to eq(['spy("Widget")'])
37
- expect(cop.offenses.map(&:line).sort).to eq([2])
26
+ expect_violation(<<-RUBY)
27
+ it do
28
+ foo = spy("Widget")
29
+ ^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
30
+ end
31
+ RUBY
38
32
  end
39
33
  end
40
34
 
@@ -42,34 +36,36 @@ describe RuboCop::Cop::RSpec::VerifiedDoubles, :config do
42
36
  let(:cop_config) { { 'IgnoreSymbolicNames' => true } }
43
37
 
44
38
  it 'ignores doubles whose name is a symbol' do
45
- inspect_source(cop, ['it do',
46
- ' foo = double(:widget)',
47
- 'end'])
48
- expect(cop.messages).to be_empty
39
+ expect_no_violations(<<-RUBY)
40
+ it do
41
+ foo = double(:widget)
42
+ end
43
+ RUBY
49
44
  end
50
45
 
51
46
  it 'still flags doubles whose name is a string' do
52
- inspect_source(cop, ['it do',
53
- ' foo = double("widget")',
54
- 'end'])
55
-
56
- expect(cop.messages.first).to eq(
57
- 'Prefer using verifying doubles over normal doubles.'
58
- )
47
+ expect_violation(<<-RUBY)
48
+ it do
49
+ foo = double("widget")
50
+ ^^^^^^^^^^^^^^^^ Prefer using verifying doubles over normal doubles.
51
+ end
52
+ RUBY
59
53
  end
60
54
  end
61
55
 
62
56
  it 'ignores doubles without a name' do
63
- inspect_source(cop, ['it do',
64
- ' foo = double',
65
- 'end'])
66
- expect(cop.messages).to be_empty
57
+ expect_no_violations(<<-RUBY)
58
+ it do
59
+ foo = double
60
+ end
61
+ RUBY
67
62
  end
68
63
 
69
64
  it 'ignores instance_doubles' do
70
- inspect_source(cop, ['it do',
71
- ' foo = instance_double("Foo")',
72
- 'end'])
73
- expect(cop.messages).to be_empty
65
+ expect_no_violations(<<-RUBY)
66
+ it do
67
+ foo = instance_double("Foo")
68
+ end
69
+ RUBY
74
70
  end
75
71
  end
@@ -7,6 +7,8 @@ if ENV['CI']
7
7
  CodeClimate::TestReporter.start
8
8
  end
9
9
 
10
+ Dir.glob(File.expand_path('support/*.rb', __dir__)).map(&method(:require))
11
+
10
12
  RSpec.configure do |config|
11
13
  config.order = :random
12
14
 
@@ -17,8 +19,11 @@ RSpec.configure do |config|
17
19
  config.mock_with :rspec do |mocks|
18
20
  mocks.syntax = :expect # Disable `should_receive` and `stub`
19
21
  end
22
+
23
+ config.include(ExpectViolation)
20
24
  end
21
25
 
22
26
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
23
27
  $LOAD_PATH.unshift(File.dirname(__FILE__))
28
+
24
29
  require 'rubocop-rspec'
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'adamantium'
4
+ require 'concord'
5
+ require 'anima'
6
+
7
+ module ExpectViolation
8
+ DEFAULT_FILENAME = 'example_spec.rb'.freeze
9
+
10
+ # rubocop:disable Metrics/AbcSize
11
+ def expect_violation(source, filename: DEFAULT_FILENAME)
12
+ expectation = Expectation.new(source)
13
+ inspect_source(cop, expectation.source, filename)
14
+ offenses = cop.offenses.map(&method(:to_assertion)).sort
15
+
16
+ if expectation.assertions.empty?
17
+ raise 'Use expect_no_violations to assert no violations'
18
+ end
19
+
20
+ expect(offenses).to eq(expectation.assertions.sort)
21
+ end
22
+
23
+ def expect_no_violations(source, filename: DEFAULT_FILENAME)
24
+ inspect_source(cop, source, filename)
25
+
26
+ expect(cop.offenses.empty?).to be(true)
27
+ end
28
+
29
+ private
30
+
31
+ def to_assertion(offense)
32
+ Expectation::Assertion.new(
33
+ message: offense.message,
34
+ line_number: offense.location.line,
35
+ column_range: offense.location.column_range
36
+ )
37
+ end
38
+
39
+ class Expectation
40
+ VIOLATION_LINE_PATTERN = /\A *\^/
41
+
42
+ VIOLATION = :violation
43
+ SOURCE = :line
44
+
45
+ include Adamantium, Concord.new(:string)
46
+
47
+ def source
48
+ source_map.to_s
49
+ end
50
+
51
+ def assertions
52
+ source_map.assertions
53
+ end
54
+
55
+ private
56
+
57
+ def source_map
58
+ tokens.reduce(Source::BLANK) do |source, (type, tokens)|
59
+ tokens.reduce(source, :"add_#{type}")
60
+ end
61
+ end
62
+ memoize :source_map
63
+
64
+ def tokens
65
+ string.each_line.chunk do |line|
66
+ next SOURCE unless line =~ VIOLATION_LINE_PATTERN
67
+
68
+ VIOLATION
69
+ end
70
+ end
71
+
72
+ class Source
73
+ include Concord.new(:lines)
74
+
75
+ BLANK = new([].freeze)
76
+
77
+ def add_line(line)
78
+ self.class.new(lines + [Line.new(text: line, number: lines.size + 1)])
79
+ end
80
+
81
+ def add_violation(violation)
82
+ self.class.new([*lines[0...-1], lines.last.add_violation(violation)])
83
+ end
84
+
85
+ def to_s
86
+ lines.map(&:text).join
87
+ end
88
+
89
+ def assertions
90
+ lines.flat_map(&:assertions)
91
+ end
92
+
93
+ class Line
94
+ DEFAULTS = { violations: [] }.freeze
95
+
96
+ include Anima.new(:text, :number, :violations)
97
+
98
+ def initialize(options)
99
+ super(DEFAULTS.merge(options))
100
+ end
101
+
102
+ def add_violation(violation)
103
+ with(violations: violations + [violation])
104
+ end
105
+
106
+ def assertions
107
+ violations.map do |violation|
108
+ Assertion.parse(
109
+ text: violation,
110
+ line_number: number
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ class Assertion
118
+ def self.parse(text:, line_number:)
119
+ parser = Parser.new(text)
120
+
121
+ new(
122
+ message: parser.message,
123
+ column_range: parser.column_range,
124
+ line_number: line_number
125
+ )
126
+ end
127
+
128
+ include Anima.new(:message, :column_range, :line_number),
129
+ Adamantium,
130
+ Comparable
131
+
132
+ def <=>(other)
133
+ to_a <=> other.to_a
134
+ end
135
+
136
+ protected
137
+
138
+ def to_a
139
+ [line_number, column_range.first, column_range.last, message]
140
+ end
141
+
142
+ class Parser
143
+ COLUMN_PATTERN = /^ *(?<carets>\^\^*) (?<message>.+)$/
144
+
145
+ include Concord.new(:text), Adamantium
146
+
147
+ def column_range
148
+ Range.new(*match.offset(:carets), true)
149
+ end
150
+
151
+ def message
152
+ match[:message]
153
+ end
154
+
155
+ private
156
+
157
+ def match
158
+ text.match(COLUMN_PATTERN)
159
+ end
160
+ memoize :match
161
+ end
162
+
163
+ private_constant(*constants(false))
164
+ end
165
+ end
166
+ end