rubocop-view_component 0.6.0 → 0.8.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 +4 -4
- data/.rubocop.yml +4 -0
- data/{CLAUDE.md → AGENTS.md} +6 -14
- data/Appraisals +5 -0
- data/README.md +50 -27
- data/config/default.yml +6 -5
- data/docs/rubydex.md +136 -0
- data/gemfiles/rubocop_minimum.gemfile +13 -0
- data/gemfiles/rubocop_minimum.gemfile.lock +162 -0
- data/lib/rubocop/cop/view_component/base.rb +56 -7
- data/lib/rubocop/cop/view_component/prefer_composition.rb +42 -9
- data/lib/rubocop/cop/view_component/prefer_private_methods.rb +46 -13
- data/lib/rubocop/cop/view_component/rspec_scope.rb +98 -0
- data/lib/rubocop/cop/view_component/supplementary_index.rb +142 -0
- data/lib/rubocop/cop/view_component/test_rendered_output.rb +107 -11
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +29 -16
- data/spec/expected_flowbite_failures.yml +64 -0
- data/spec/expected_govuk_failures.yml +10 -12
- data/spec/expected_polaris_failures.yml +2 -0
- data/spec/expected_primer_failures.yml +60 -2
- data/spec/rubocop/cop/view_component/base_spec.rb +108 -0
- data/spec/rubocop/cop/view_component/component_suffix_spec.rb +0 -58
- data/spec/rubocop/cop/view_component/missing_preview_spec.rb +1 -42
- data/spec/rubocop/cop/view_component/prefer_composition_spec.rb +91 -4
- data/spec/rubocop/cop/view_component/prefer_private_methods_spec.rb +126 -4
- data/spec/rubocop/cop/view_component/test_rendered_output_spec.rb +287 -4
- data/spec/spec_helper.rb +10 -0
- data/spec/support/project_index_helpers.rb +71 -0
- data/verification/flowbite_rubocop_config.yml +12 -0
- data/verification/govuk_rubocop_config.yml +3 -6
- data/verification/libraries.yml +4 -0
- data/verification/polaris_rubocop_config.yml +3 -7
- data/verification/primer_rubocop_config.yml +5 -7
- metadata +29 -5
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
3
6
|
RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
4
7
|
let(:config) { RuboCop::Config.new }
|
|
5
8
|
|
|
@@ -54,6 +57,77 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
|
|
60
|
+
context "when helper methods are made private with an explicit list" do
|
|
61
|
+
it "does not register offense for the listed methods" do
|
|
62
|
+
expect_no_offenses(<<~RUBY)
|
|
63
|
+
class CardComponent < ViewComponent::Base
|
|
64
|
+
def initialize(title)
|
|
65
|
+
@title = title
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def formatted_title
|
|
69
|
+
@title.upcase
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def helper
|
|
73
|
+
"helper"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private :formatted_title, :helper
|
|
77
|
+
end
|
|
78
|
+
RUBY
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "does not change the visibility of methods defined after the list" do
|
|
82
|
+
expect_offense(<<~RUBY)
|
|
83
|
+
class CardComponent < ViewComponent::Base
|
|
84
|
+
def formatted_title
|
|
85
|
+
"title"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private :formatted_title
|
|
89
|
+
|
|
90
|
+
def other_helper
|
|
91
|
+
^^^^^^^^^^^^^^^^ ViewComponent/PreferPrivateMethods: Consider making `other_helper` private. Only ViewComponent interface methods should be public.
|
|
92
|
+
"other"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
RUBY
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "flags a method made public again with an explicit list" do
|
|
99
|
+
expect_offense(<<~RUBY)
|
|
100
|
+
class CardComponent < ViewComponent::Base
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def formatted_title
|
|
104
|
+
^^^^^^^^^^^^^^^^^^^ ViewComponent/PreferPrivateMethods: Consider making `formatted_title` private. Only ViewComponent interface methods should be public.
|
|
105
|
+
"title"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
public :formatted_title
|
|
109
|
+
end
|
|
110
|
+
RUBY
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context "when a method is defined with `private def`" do
|
|
115
|
+
it "flags later public methods but not the `private def` method" do
|
|
116
|
+
expect_offense(<<~RUBY)
|
|
117
|
+
class CardComponent < ViewComponent::Base
|
|
118
|
+
private def formatted_title
|
|
119
|
+
"title"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def other_helper
|
|
123
|
+
^^^^^^^^^^^^^^^^ ViewComponent/PreferPrivateMethods: Consider making `other_helper` private. Only ViewComponent interface methods should be public.
|
|
124
|
+
"other"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
RUBY
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
57
131
|
context "with allowed public interface methods" do
|
|
58
132
|
it "allows initialize" do
|
|
59
133
|
expect_no_offenses(<<~RUBY)
|
|
@@ -147,8 +221,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
147
221
|
"AllCops" => { "DisplayCopNames" => true },
|
|
148
222
|
"ViewComponent/PreferPrivateMethods" => {
|
|
149
223
|
"AllowedPublicMethods" => %w[initialize call],
|
|
150
|
-
"AllowedPublicMethodPatterns" => ["^render_", "^with_"]
|
|
151
|
-
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
224
|
+
"AllowedPublicMethodPatterns" => ["^render_", "^with_"]
|
|
152
225
|
}
|
|
153
226
|
)
|
|
154
227
|
end
|
|
@@ -190,8 +263,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
190
263
|
"AllCops" => { "DisplayCopNames" => true },
|
|
191
264
|
"ViewComponent/PreferPrivateMethods" => {
|
|
192
265
|
"AllowedPublicMethods" => %w[initialize call custom_public_method],
|
|
193
|
-
"AllowedPublicMethodPatterns" => []
|
|
194
|
-
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
266
|
+
"AllowedPublicMethodPatterns" => []
|
|
195
267
|
}
|
|
196
268
|
)
|
|
197
269
|
end
|
|
@@ -260,6 +332,56 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
260
332
|
end
|
|
261
333
|
end
|
|
262
334
|
|
|
335
|
+
context "with AdditionalSourcePaths" do
|
|
336
|
+
let(:engine_dir) { Dir.mktmpdir("engine") }
|
|
337
|
+
|
|
338
|
+
before do
|
|
339
|
+
FileUtils.mkdir_p(File.join(engine_dir, "app", "components"))
|
|
340
|
+
File.write(
|
|
341
|
+
File.join(engine_dir, "app", "components", "engine_base_component.rb"),
|
|
342
|
+
"class EngineBaseComponent < ViewComponent::Base\nend\n"
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
after { FileUtils.remove_entry(engine_dir) }
|
|
347
|
+
|
|
348
|
+
context "when the engine source path is configured" do
|
|
349
|
+
let(:config) do
|
|
350
|
+
RuboCop::Config.new(
|
|
351
|
+
"AllCops" => { "DisplayCopNames" => true },
|
|
352
|
+
"ViewComponent/PreferPrivateMethods" => {
|
|
353
|
+
"AdditionalSourcePaths" => [File.join(engine_dir, "app", "components")]
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it "resolves ancestry through the additional source paths" do
|
|
359
|
+
expect_offense(<<~RUBY)
|
|
360
|
+
class CardComponent < EngineBaseComponent
|
|
361
|
+
def helper
|
|
362
|
+
^^^^^^^^^^ ViewComponent/PreferPrivateMethods: Consider making `helper` private. Only ViewComponent interface methods should be public.
|
|
363
|
+
"helper"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
RUBY
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
context "when the engine source path is not configured" do
|
|
371
|
+
let(:config) { RuboCop::Config.new }
|
|
372
|
+
|
|
373
|
+
it "does not detect the class as a ViewComponent" do
|
|
374
|
+
expect_no_offenses(<<~RUBY)
|
|
375
|
+
class CardComponent < EngineBaseComponent
|
|
376
|
+
def helper
|
|
377
|
+
"helper"
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
RUBY
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
263
385
|
context "with template files" do
|
|
264
386
|
let(:component_file) { "spec/fixtures/components/template_method_component.rb" }
|
|
265
387
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
4
|
+
include ViewComponent::ProjectIndexHelpers
|
|
5
|
+
|
|
4
6
|
let(:config) { RuboCop::Config.new }
|
|
7
|
+
let(:project_index) { build_index }
|
|
5
8
|
|
|
6
9
|
context "with Minitest-style tests" do
|
|
7
10
|
context "when test instantiates a component but doesn't render" do
|
|
@@ -17,17 +20,37 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
context "when test instantiates a non-Component-suffixed class" do
|
|
20
|
-
it "registers an offense" do
|
|
23
|
+
it "registers an offense for an indexed component" do
|
|
21
24
|
expect_offense(<<~RUBY)
|
|
22
25
|
def test_toggle
|
|
23
26
|
^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
24
|
-
component =
|
|
27
|
+
component = UserComponent.new
|
|
25
28
|
assert component.active?
|
|
26
29
|
end
|
|
27
30
|
RUBY
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
|
|
34
|
+
context "when test instantiates a non-ViewComponent value object" do
|
|
35
|
+
let(:project_index) do
|
|
36
|
+
build_index(
|
|
37
|
+
"file:///month.rb" => <<~RUBY
|
|
38
|
+
class Month
|
|
39
|
+
end
|
|
40
|
+
RUBY
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "does not register an offense" do
|
|
45
|
+
expect_no_offenses(<<~RUBY)
|
|
46
|
+
def test_month
|
|
47
|
+
month = Month.new
|
|
48
|
+
assert month.active?
|
|
49
|
+
end
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
31
54
|
context "when test instantiates a component and uses render_inline" do
|
|
32
55
|
it "does not register an offense" do
|
|
33
56
|
expect_no_offenses(<<~RUBY)
|
|
@@ -50,6 +73,18 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
50
73
|
end
|
|
51
74
|
end
|
|
52
75
|
|
|
76
|
+
context "when test instantiates a component and uses render_in_view_context" do
|
|
77
|
+
it "does not register an offense" do
|
|
78
|
+
expect_no_offenses(<<~RUBY)
|
|
79
|
+
def test_form_input
|
|
80
|
+
render_in_view_context do
|
|
81
|
+
render(UserComponent.new("hello"))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
RUBY
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
53
88
|
context "when test doesn't instantiate any constants" do
|
|
54
89
|
it "does not register an offense" do
|
|
55
90
|
expect_no_offenses(<<~RUBY)
|
|
@@ -84,16 +119,46 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
84
119
|
end
|
|
85
120
|
|
|
86
121
|
context "when test instantiates a namespaced component" do
|
|
87
|
-
it "registers an offense" do
|
|
122
|
+
it "registers an offense for an indexed component" do
|
|
88
123
|
expect_offense(<<~RUBY)
|
|
89
124
|
def test_namespaced
|
|
90
125
|
^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
91
|
-
component =
|
|
126
|
+
component = UserComponent.new("hello")
|
|
92
127
|
assert_equal "HELLO", component.formatted_title
|
|
93
128
|
end
|
|
94
129
|
RUBY
|
|
95
130
|
end
|
|
96
131
|
end
|
|
132
|
+
|
|
133
|
+
context "when the component is built to assert an error" do
|
|
134
|
+
it "does not register an offense for assert_raises" do
|
|
135
|
+
expect_no_offenses(<<~RUBY)
|
|
136
|
+
def test_raises_for_invalid_input
|
|
137
|
+
assert_raises(ArgumentError) { UserComponent.new(nil) }
|
|
138
|
+
end
|
|
139
|
+
RUBY
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "does not register an offense for a do/end assert_raises block" do
|
|
143
|
+
expect_no_offenses(<<~RUBY)
|
|
144
|
+
def test_raises_for_invalid_input
|
|
145
|
+
assert_raises(ArgumentError) do
|
|
146
|
+
UserComponent.new(nil)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
RUBY
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "registers an offense when a component is built outside the assertion" do
|
|
153
|
+
expect_offense(<<~RUBY)
|
|
154
|
+
def test_mixed
|
|
155
|
+
^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
156
|
+
component = UserComponent.new("hello")
|
|
157
|
+
assert_raises(ArgumentError) { component.invalid! }
|
|
158
|
+
end
|
|
159
|
+
RUBY
|
|
160
|
+
end
|
|
161
|
+
end
|
|
97
162
|
end
|
|
98
163
|
|
|
99
164
|
context "with TestPaths configured" do
|
|
@@ -154,6 +219,18 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
154
219
|
end
|
|
155
220
|
end
|
|
156
221
|
|
|
222
|
+
context "when it block instantiates a component and uses render_in_view_context" do
|
|
223
|
+
it "does not register an offense" do
|
|
224
|
+
expect_no_offenses(<<~RUBY)
|
|
225
|
+
it "renders a form" do
|
|
226
|
+
render_in_view_context do
|
|
227
|
+
render(UserComponent.new("hello"))
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
RUBY
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
157
234
|
context "when it block doesn't instantiate components" do
|
|
158
235
|
it "does not register an offense" do
|
|
159
236
|
expect_no_offenses(<<~RUBY)
|
|
@@ -164,5 +241,211 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
164
241
|
RUBY
|
|
165
242
|
end
|
|
166
243
|
end
|
|
244
|
+
|
|
245
|
+
context "when it block uses described_class inside a describe block" do
|
|
246
|
+
it "registers an offense" do
|
|
247
|
+
expect_offense(<<~RUBY)
|
|
248
|
+
describe UserComponent do
|
|
249
|
+
it "formats the title" do
|
|
250
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
251
|
+
component = described_class.new("hello")
|
|
252
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
RUBY
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it "registers an offense for RSpec.describe" do
|
|
259
|
+
expect_offense(<<~RUBY)
|
|
260
|
+
RSpec.describe UserComponent do
|
|
261
|
+
it "formats the title" do
|
|
262
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
263
|
+
expect(described_class.new("hello").formatted_title).to eq("HELLO")
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
RUBY
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it "registers an offense inside a nested context" do
|
|
270
|
+
expect_offense(<<~RUBY)
|
|
271
|
+
describe UserComponent do
|
|
272
|
+
context "when rendered" do
|
|
273
|
+
it "formats the title" do
|
|
274
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
275
|
+
component = described_class.new("hello")
|
|
276
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
RUBY
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "does not register an offense when using render_inline" do
|
|
284
|
+
expect_no_offenses(<<~RUBY)
|
|
285
|
+
describe UserComponent do
|
|
286
|
+
it "formats the title" do
|
|
287
|
+
render_inline described_class.new("hello")
|
|
288
|
+
expect(page).to have_text("HELLO")
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
RUBY
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
context "when the component is built to assert an error" do
|
|
296
|
+
it "does not register an offense for described_class" do
|
|
297
|
+
expect_no_offenses(<<~RUBY)
|
|
298
|
+
describe UserComponent do
|
|
299
|
+
it "raises for invalid input" do
|
|
300
|
+
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
RUBY
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "does not register an offense for a component constant" do
|
|
307
|
+
expect_no_offenses(<<~RUBY)
|
|
308
|
+
it "raises for invalid input" do
|
|
309
|
+
expect { UserComponent.new(nil) }.to raise_error(ArgumentError)
|
|
310
|
+
end
|
|
311
|
+
RUBY
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "does not register an offense for not_to raise_error" do
|
|
315
|
+
expect_no_offenses(<<~RUBY)
|
|
316
|
+
it "does not raise for valid input" do
|
|
317
|
+
expect { UserComponent.new("hello") }.not_to raise_error
|
|
318
|
+
end
|
|
319
|
+
RUBY
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it "does not register an offense when raise_error takes a block" do
|
|
323
|
+
expect_no_offenses(<<~RUBY)
|
|
324
|
+
it "raises with a message" do
|
|
325
|
+
expect { UserComponent.new(nil) }.to raise_error(ArgumentError) do |error|
|
|
326
|
+
expect(error.message).to eq("invalid")
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
RUBY
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
context "when a component is built by a let helper" do
|
|
334
|
+
it "registers an offense" do
|
|
335
|
+
expect_offense(<<~RUBY)
|
|
336
|
+
describe UserComponent do
|
|
337
|
+
let(:component) { UserComponent.new("hello") }
|
|
338
|
+
|
|
339
|
+
it "formats the title" do
|
|
340
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
341
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
RUBY
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it "registers an offense for a subject using described_class" do
|
|
348
|
+
expect_offense(<<~RUBY)
|
|
349
|
+
describe UserComponent do
|
|
350
|
+
subject(:component) { described_class.new("hello") }
|
|
351
|
+
|
|
352
|
+
it "formats the title" do
|
|
353
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
354
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
RUBY
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it "registers an offense when the let is defined in an outer group" do
|
|
361
|
+
expect_offense(<<~RUBY)
|
|
362
|
+
describe UserComponent do
|
|
363
|
+
let(:component) { UserComponent.new("hello") }
|
|
364
|
+
|
|
365
|
+
context "when rendered" do
|
|
366
|
+
it "formats the title" do
|
|
367
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^ ViewComponent/TestRenderedOutput: Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
368
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
RUBY
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
it "does not register an offense when the helper renders output" do
|
|
376
|
+
expect_no_offenses(<<~RUBY)
|
|
377
|
+
describe UserComponent do
|
|
378
|
+
let(:rendered) { render_inline(UserComponent.new("hello")) }
|
|
379
|
+
|
|
380
|
+
it "formats the title" do
|
|
381
|
+
expect(rendered).to have_text("HELLO")
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
RUBY
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
it "does not register an offense when the helper builds another object" do
|
|
388
|
+
expect_no_offenses(<<~RUBY)
|
|
389
|
+
describe UserComponent do
|
|
390
|
+
let(:component) { double("component") }
|
|
391
|
+
|
|
392
|
+
it "formats the title" do
|
|
393
|
+
expect(component.formatted_title).to eq("HELLO")
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
RUBY
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
it "does not register an offense when the helper is rendered with render_inline" do
|
|
400
|
+
expect_no_offenses(<<~RUBY)
|
|
401
|
+
describe UserComponent do
|
|
402
|
+
let(:component) { UserComponent.new("hello") }
|
|
403
|
+
|
|
404
|
+
it "formats the title" do
|
|
405
|
+
render_inline(component)
|
|
406
|
+
expect(page).to have_text("HELLO")
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
RUBY
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it "prefers the innermost helper definition" do
|
|
413
|
+
expect_no_offenses(<<~RUBY)
|
|
414
|
+
describe UserComponent do
|
|
415
|
+
let(:component) { UserComponent.new("hello") }
|
|
416
|
+
|
|
417
|
+
context "with a stub" do
|
|
418
|
+
let(:component) { double("component") }
|
|
419
|
+
|
|
420
|
+
it "formats the title" do
|
|
421
|
+
expect(component).to be_present
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
RUBY
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
context "when the described class is not a ViewComponent" do
|
|
431
|
+
let(:project_index) do
|
|
432
|
+
build_index(
|
|
433
|
+
"file:///month.rb" => <<~RUBY
|
|
434
|
+
class Month
|
|
435
|
+
end
|
|
436
|
+
RUBY
|
|
437
|
+
)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "does not register an offense" do
|
|
441
|
+
expect_no_offenses(<<~RUBY)
|
|
442
|
+
describe Month do
|
|
443
|
+
it "formats the month" do
|
|
444
|
+
month = described_class.new
|
|
445
|
+
expect(month.active?).to be(true)
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
RUBY
|
|
449
|
+
end
|
|
167
450
|
end
|
|
168
451
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "rubocop-view_component"
|
|
4
4
|
require "rubocop/rspec/support"
|
|
5
|
+
require_relative "support/project_index_helpers"
|
|
5
6
|
|
|
6
7
|
RSpec.configure do |config|
|
|
7
8
|
config.disable_monkey_patching!
|
|
@@ -11,4 +12,13 @@ RSpec.configure do |config|
|
|
|
11
12
|
|
|
12
13
|
config.order = :random
|
|
13
14
|
Kernel.srand config.seed
|
|
15
|
+
|
|
16
|
+
config.include ViewComponent::ProjectIndexHelpers
|
|
17
|
+
|
|
18
|
+
config.before do |example|
|
|
19
|
+
next unless example.metadata[:config]
|
|
20
|
+
|
|
21
|
+
graph = build_index
|
|
22
|
+
cop.project_index = ViewComponent::ProjectIndexHelpers::LazyIndex.new(graph, cop)
|
|
23
|
+
end
|
|
14
24
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubydex"
|
|
4
|
+
|
|
5
|
+
module ViewComponent
|
|
6
|
+
module ProjectIndexHelpers
|
|
7
|
+
DEFAULT_SOURCES = {
|
|
8
|
+
"file:///view_component.rb" => <<~RUBY,
|
|
9
|
+
module ViewComponent
|
|
10
|
+
class Base
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
RUBY
|
|
14
|
+
"file:///application_component.rb" => <<~RUBY,
|
|
15
|
+
class ApplicationComponent < ViewComponent::Base
|
|
16
|
+
end
|
|
17
|
+
RUBY
|
|
18
|
+
"file:///user_component.rb" => <<~RUBY
|
|
19
|
+
class UserComponent < ApplicationComponent
|
|
20
|
+
end
|
|
21
|
+
RUBY
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def build_index(sources = {})
|
|
25
|
+
graph = Rubydex::Graph.new
|
|
26
|
+
DEFAULT_SOURCES.merge(sources).each { |uri, source| graph.index_source(uri, source, "ruby") }
|
|
27
|
+
graph.resolve
|
|
28
|
+
graph
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Wraps a graph to lazily index the source being inspected when
|
|
32
|
+
# resolve_constant is called. This mirrors how the real RuboCop runner
|
|
33
|
+
# indexes all project files, including the one being inspected.
|
|
34
|
+
class LazyIndex
|
|
35
|
+
def initialize(base_graph, cop)
|
|
36
|
+
@base_graph = base_graph
|
|
37
|
+
@cop = cop
|
|
38
|
+
@rebuilt = false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_constant(name, nesting)
|
|
42
|
+
result = @base_graph.resolve_constant(name, nesting)
|
|
43
|
+
return result if result
|
|
44
|
+
|
|
45
|
+
rebuild_once
|
|
46
|
+
@base_graph.resolve_constant(name, nesting)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def method_missing(name, ...)
|
|
50
|
+
@base_graph.send(name, ...)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def respond_to_missing?(name, include_private = false)
|
|
54
|
+
@base_graph.respond_to?(name, include_private) || super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def rebuild_once
|
|
60
|
+
return if @rebuilt
|
|
61
|
+
|
|
62
|
+
@rebuilt = true
|
|
63
|
+
source = @cop&.processed_source
|
|
64
|
+
return unless source&.raw_source
|
|
65
|
+
|
|
66
|
+
@base_graph.index_source("file:///test_source.rb", source.raw_source, "ruby")
|
|
67
|
+
@base_graph.resolve
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- GovukComponent::Base
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
7
4
|
|
|
8
5
|
# x-govuk components do not use ViewComponent previews
|
|
9
6
|
ViewComponent/MissingPreview:
|
data/verification/libraries.yml
CHANGED
|
@@ -9,3 +9,7 @@ govuk:
|
|
|
9
9
|
polaris:
|
|
10
10
|
tarball_url: https://github.com/baoagency/polaris_view_components/archive/refs/heads/main.tar.gz
|
|
11
11
|
display_name: baoagency/polaris_view_components
|
|
12
|
+
|
|
13
|
+
flowbite:
|
|
14
|
+
tarball_url: https://github.com/substancelab/flowbite-components/archive/refs/heads/main.tar.gz
|
|
15
|
+
display_name: substancelab/flowbite-components
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- Polaris::Component
|
|
7
|
-
- Component
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
8
4
|
|
|
9
5
|
ViewComponent/MissingPreview:
|
|
10
6
|
PreviewPaths:
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- Primer::Component
|
|
7
|
-
- Primer::BaseComponent
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
8
4
|
|
|
9
5
|
# Primer seems to intentionally omit the Component suffix from most class names
|
|
10
6
|
# (116 out of 131 components), so this cop is not useful here.
|
|
@@ -31,3 +27,5 @@ ViewComponent/MissingPreview:
|
|
|
31
27
|
- previews
|
|
32
28
|
Exclude:
|
|
33
29
|
- app/components/primer/base_component.rb
|
|
30
|
+
# Deprecated wrapper with no preview
|
|
31
|
+
- app/components/primer/navigation/tab_component.rb
|