actionview-component 1.7.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE +3 -8
  3. data/.github/PULL_REQUEST_TEMPLATE +2 -4
  4. data/.github/workflows/ruby_on_rails.yml +2 -2
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.md +54 -0
  8. data/CONTRIBUTING.md +5 -14
  9. data/Gemfile.lock +16 -1
  10. data/README.md +129 -53
  11. data/actionview-component.gemspec +1 -0
  12. data/lib/action_view/component/base.rb +29 -11
  13. data/lib/action_view/component/preview.rb +7 -15
  14. data/lib/action_view/component/railtie.rb +0 -1
  15. data/lib/action_view/component/render_monkey_patch.rb +11 -6
  16. data/lib/action_view/component/test_helpers.rb +12 -2
  17. data/lib/action_view/component/version.rb +1 -1
  18. data/lib/rails/generators/component/component_generator.rb +8 -4
  19. data/lib/rails/generators/erb/component_generator.rb +27 -0
  20. data/lib/rails/generators/{component → erb}/templates/component.html.erb.tt +0 -0
  21. data/lib/rails/generators/haml/component_generator.rb +27 -0
  22. data/lib/rails/generators/haml/templates/component.html.haml.tt +5 -0
  23. data/lib/rails/generators/rspec/component_generator.rb +1 -1
  24. data/lib/rails/generators/slim/component_generator.rb +27 -0
  25. data/lib/rails/generators/slim/templates/component.html.slim +5 -0
  26. data/lib/rails/generators/test_unit/component_generator.rb +1 -1
  27. data/lib/railties/lib/rails.rb +0 -1
  28. data/lib/railties/lib/rails/components_controller.rb +5 -1
  29. data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -1
  30. metadata +22 -5
  31. data/lib/railties/lib/rails/component_examples_controller.rb +0 -9
  32. data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '085434585e1e3fa1493bbc7bd90439b1645015acd66a713a33545c0eb4677dc4'
4
- data.tar.gz: 34ec8d8f390471689fbd1085c94885b74c4f9a1b0b7d9b78650dae506520dd13
3
+ metadata.gz: 2d2c0dab4ae3674f45f3f830257f49b108f51923e98a3de1182667f71a951cb2
4
+ data.tar.gz: 220a51f5dee367a6bf22e3a5306d17a2e302c5326cf4ad4a5f271c2ff0edb864
5
5
  SHA512:
6
- metadata.gz: 89e3c7d8b6c1d67a29f2ad67a032e424c42f57dd719d5b8cbde01b167385d9d2aeb5feb8fd77fcd9284e0a33da828b935edeebbb758c7cd68951178eea2d3b14
7
- data.tar.gz: bc76f28233c72f7d5ff3a818429e8c763649db3ec46e406b554070cd456b73ae923905ecb94d3b37949ca5c6b0960890594a341e72a9755343462cf4c5c24cff
6
+ metadata.gz: f9c79d7c62166ef2fb8f1cba5220d1599c8d6129252e09aec47917de3a7db1c3d9d6ef70da8231dc3389eac9e0136b2b533a972867a12ad975f8f5db9f49d5cc
7
+ data.tar.gz: efc71bebe0aeba60aeefdc00ae50d7485e2a28334c7b4764d359e84402e740f8aa0b66a36035be5223a2114c1e432c50c071d0924c2a3f90d8658f5e76a330d5
@@ -6,17 +6,12 @@
6
6
  ### Motivation
7
7
 
8
8
  <!-- What would you like to do with this feature? Can you provide
9
- context or references to similar behavior in other libraries. -->
9
+ context or references to similar behavior in other libraries? -->
10
10
 
11
-
12
-
13
-
14
-
15
- <!-- **** Filing a Bug Report? Include these sections. **** -->
11
+ <!-- **** Filing a Bug Report? Include these sections: **** -->
16
12
 
17
13
  ### Steps to reproduce
18
- <!-- Provide an series of steps or, better yet, a link to a repo to
19
- demonstrate the bug you've identified. -->
14
+ <!-- Provide a series of steps or, better yet, a link to a repo to demonstrate the bug you've identified. -->
20
15
 
21
16
  ### Expected behavior
22
17
  <!-- Tell us what should happen -->
@@ -1,4 +1,4 @@
1
- <!-- https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
1
+ <!-- See https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
2
2
 
3
3
  ### Summary
4
4
 
@@ -14,6 +14,4 @@ request, mention that information here. This could include
14
14
  benchmarks, or other information.
15
15
 
16
16
  If you are updating any of the CHANGELOG files or are asked to update the
17
- CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.
18
-
19
- Thanks for contributing to actionview-component! -->
17
+ CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file. -->
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  matrix:
10
10
  rails_version: [5.0.0, 5.2.3, 6.0.0, master]
11
- ruby_version: [2.4.x, 2.5.x, 2.6.x]
11
+ ruby_version: [2.5.x, 2.6.x, 2.7.x]
12
12
  exclude:
13
13
  - rails_version: master
14
14
  ruby_version: 2.4.x
@@ -22,7 +22,7 @@ jobs:
22
22
  ruby-version: ${{ matrix.ruby_version }}
23
23
  - name: Build and test with Rake
24
24
  run: |
25
- gem install bundler:1.14.0
25
+ gem install bundler:1.17.3
26
26
  bundle update
27
27
  bundle install --jobs 4 --retry 3
28
28
  bundle exec rake
data/.gitignore CHANGED
@@ -11,6 +11,7 @@
11
11
  /tmp/
12
12
  /test/log/*
13
13
  /test/app/tmp/*
14
+ /test/tmp/*
14
15
 
15
16
  # Used by dotenv library to load environment variables.
16
17
  # .env
data/.rubocop.yml CHANGED
@@ -2,3 +2,7 @@ inherit_gem:
2
2
  rubocop-github:
3
3
  - config/default.yml
4
4
  - config/rails.yml
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - "test/tmp/**/*"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,57 @@
1
+ # v1.11.0
2
+
3
+ * Add support for Capybara matchers.
4
+
5
+ *Joel Hawksley*
6
+
7
+ * Add erb, haml, & slim template generators
8
+
9
+ *Asger Behncke Jacobsen*
10
+
11
+ # v1.10.0
12
+
13
+ * Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))`
14
+
15
+ *Joel Hawksley*
16
+
17
+ # v1.9.0
18
+
19
+ * Remove initializer requirement for Ruby 2.7+
20
+
21
+ *Dylan Clark*
22
+
23
+ # v1.8.1
24
+
25
+ * Run validation checks before calling `#render?`.
26
+
27
+ *Ash Wilson*
28
+
29
+ # v1.8.0
30
+
31
+ * Remove the unneeded ComponentExamplesController and simplify preview rendering.
32
+
33
+ *Jon Palmer*
34
+
35
+ * Add `#render?` hook to easily allow components to be no-ops.
36
+
37
+ *Kyle Fox*
38
+
39
+ * Don't assume ApplicationController exists.
40
+
41
+ *Jon Palmer*
42
+
43
+ * Allow some additional checks to overrided render?
44
+
45
+ *Sergey Malykh*
46
+
47
+ * Fix generator placing namespaced components in the root directory.
48
+
49
+ *Asger Behncke Jacobsen*
50
+
51
+ * Fix cache test.
52
+
53
+ *Sergey Malykh*
54
+
1
55
  # v1.7.0
2
56
 
3
57
  * Simplify validation of templates and compilation.
data/CONTRIBUTING.md CHANGED
@@ -1,4 +1,4 @@
1
- ## Contributing
1
+ # Contributing
2
2
 
3
3
  [fork]: https://github.com/github/actionview-component/fork
4
4
  [pr]: https://github.com/github/actionview-component/compare
@@ -15,9 +15,11 @@ Please note that this project is released with a [Contributor Code of Conduct][c
15
15
 
16
16
  0. [Fork][fork] and clone the repository
17
17
  0. Configure and install the dependencies: `bundle`
18
- 0. Make sure the tests pass on your machine: `rake`
18
+ 0. Make sure the tests pass on your machine: `bundle exec rake`
19
19
  0. Create a new branch: `git checkout -b my-branch-name`
20
20
  0. Make your change, add tests, and make sure the tests still pass
21
+ 0. Add an entry to the top of `CHANGELOG.md` for your changes
22
+ 0. If it's your first time contributing, add yourself to the contributors at the bottom of `README.md`
21
23
  0. Push to your fork and [submit a pull request][pr]
22
24
  0. Pat your self on the back and wait for your pull request to be reviewed and merged.
23
25
 
@@ -32,24 +34,13 @@ Here are a few things you can do that will increase the likelihood of your pull
32
34
  If you are the current maintainer of this gem:
33
35
 
34
36
  1. Create a branch for the release: `git checkout -b release-vxx.xx.xx`
35
- 1. Bump gem version in `lib/action_view/component/version.rb`.
37
+ 1. Bump gem version in `lib/action_view/component/version.rb`. Try to adhere to SemVer.
36
38
  1. Add version heading/entries to `CHANGELOG.md`.
37
39
  1. Make sure your local dependencies are up to date: `bundle`
38
40
  1. Ensure that tests are green: `bundle exec rake`
39
- 1. Build a test gem `GEM_VERSION=$(git describe --tags 2>/dev/null | sed 's/-/./g' | sed 's/v//') gem build actionview-component.gemspec`
40
- 1. Test the test gem:
41
- 1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
42
- 1. Install the new gem locally
43
- 1. Test behavior locally, branch deploy, whatever needs to happen
44
41
  1. Make a PR to github/actionview-component.
45
42
  1. Build a local gem: `gem build actionview-component.gemspec`
46
43
  1. Merge github/actionview-component PR
47
44
  1. Tag and push: `git tag vx.xx.xx; git push --tags`
48
45
  1. Create a GitHub release with the pushed tag (https://github.com/github/actionview-component/releases/new) and populate it with a list of the commits from `git log --pretty=format:"- %s" --reverse refs/tags/[OLD TAG]...refs/tags/[NEW TAG]`
49
46
  1. Push to rubygems.org -- `gem push actionview-component-VERSION.gem`
50
-
51
- ## Resources
52
-
53
- - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
54
- - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
55
- - [GitHub Help](https://help.github.com)
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.7.0)
4
+ actionview-component (1.11.0)
5
+ capybara (>= 3.26)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
@@ -61,6 +62,8 @@ GEM
61
62
  minitest (~> 5.1)
62
63
  tzinfo (~> 1.1)
63
64
  zeitwerk (~> 2.1, >= 2.1.8)
65
+ addressable (2.7.0)
66
+ public_suffix (>= 2.0.2, < 5.0)
64
67
  ast (2.4.0)
65
68
  better_html (1.0.14)
66
69
  actionview (>= 4.0)
@@ -71,6 +74,14 @@ GEM
71
74
  parser (>= 2.4)
72
75
  smart_properties
73
76
  builder (3.2.3)
77
+ capybara (3.31.0)
78
+ addressable
79
+ mini_mime (>= 0.1.3)
80
+ nokogiri (~> 1.8)
81
+ rack (>= 1.6.0)
82
+ rack-test (>= 0.6.3)
83
+ regexp_parser (~> 1.5)
84
+ xpath (~> 3.2)
74
85
  concurrent-ruby (1.1.5)
75
86
  crass (1.0.5)
76
87
  erubi (1.8.0)
@@ -101,6 +112,7 @@ GEM
101
112
  parallel (1.17.0)
102
113
  parser (2.6.3.0)
103
114
  ast (~> 2.4.0)
115
+ public_suffix (4.0.3)
104
116
  rack (2.0.8)
105
117
  rack-test (1.1.0)
106
118
  rack (>= 1.0, < 3)
@@ -132,6 +144,7 @@ GEM
132
144
  thor (>= 0.20.3, < 2.0)
133
145
  rainbow (3.0.0)
134
146
  rake (10.5.0)
147
+ regexp_parser (1.6.0)
135
148
  rubocop (0.74.0)
136
149
  jaro_winkler (~> 1.5.1)
137
150
  parallel (~> 1.10)
@@ -166,6 +179,8 @@ GEM
166
179
  websocket-driver (0.7.1)
167
180
  websocket-extensions (>= 0.1.0)
168
181
  websocket-extensions (0.1.4)
182
+ xpath (3.2.0)
183
+ nokogiri (~> 1.8)
169
184
  zeitwerk (2.1.10)
170
185
 
171
186
  PLATFORMS
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ _Note: This gem is in the process of a name / API change, see https://github.com/github/actionview-component/issues/206_
2
+
1
3
  # ActionView::Component
2
4
  `ActionView::Component` is a framework for building view components in Rails.
3
5
 
@@ -15,7 +17,7 @@ As the goal of this gem is to be upstreamed into Rails, it is designed to integr
15
17
 
16
18
  ## Compatibility
17
19
 
18
- `actionview-component` is tested for compatibility with combinations of Ruby `2.4`/`2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
20
+ `actionview-component` is tested for compatibility with combinations of Ruby `2.5`/`2.6`/`2.7` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
19
21
 
20
22
  ## Installation
21
23
  Add this line to your application's Gemfile:
@@ -117,6 +119,18 @@ bin/rails generate component Example title content
117
119
  create app/components/example_component.html.erb
118
120
  ```
119
121
 
122
+ `ActionView::Component` includes template generators for the `erb`, `haml`, and `slim` template engines and will use the template engine specified in your Rails config (`config.generators.template_engine`) by default.
123
+
124
+ If you want to override this behavior, you can pass the template engine as an option to the generator:
125
+
126
+ ```bash
127
+ bin/rails generate component Example title content --template-engine slim
128
+ invoke test_unit
129
+ create test/components/example_component_test.rb
130
+ create app/components/example_component.rb
131
+ create app/components/example_component.html.slim
132
+ ```
133
+
120
134
  #### Implementation
121
135
 
122
136
  An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
@@ -144,7 +158,7 @@ end
144
158
  We can render it in a view as:
145
159
 
146
160
  ```erb
147
- <%= render(TestComponent, title: "my title") do %>
161
+ <%= render(TestComponent.new(title: "my title")) do %>
148
162
  Hello, World!
149
163
  <% end %>
150
164
  ```
@@ -155,42 +169,12 @@ Which returns:
155
169
  <span title="my title">Hello, World!</span>
156
170
  ```
157
171
 
158
- ##### Supported `render` syntaxes
159
-
160
- Components can be rendered via:
161
-
162
- `render(TestComponent, foo: :bar)`
163
-
164
- `render(component: TestComponent, locals: { foo: :bar })`
165
-
166
- **Rendering components through models**
167
-
168
- Passing model instances will cause `render` to look for its respective component class.
169
-
170
- The component is instantiated with the rendered model instance.
171
-
172
- Example for a `Post` model:
173
-
174
- `render(@post)`
175
-
176
- ```ruby
177
- class PostComponent < ActionView::Component::Base
178
- def initialize(post)
179
- @post = post
180
- end
181
- end
182
- ```
183
-
184
- The following syntax has been deprecated and will be removed in v2.0.0:
185
-
186
- `render(TestComponent.new(foo: :bar))`
187
-
188
172
  #### Error case
189
173
 
190
174
  If the component is rendered with a blank title:
191
175
 
192
176
  ```erb
193
- <%= render(TestComponent, title: "") do %>
177
+ <%= render(TestComponent.new(title: "")) do %>
194
178
  Hello, World!
195
179
  <% end %>
196
180
  ```
@@ -201,7 +185,6 @@ An error will be raised:
201
185
 
202
186
  #### Content Areas
203
187
 
204
-
205
188
  A component can declare additional content areas to be rendered in the component. For example:
206
189
 
207
190
  `app/components/modal_component.rb`:
@@ -228,7 +211,7 @@ end
228
211
  We can render it in a view as:
229
212
 
230
213
  ```erb
231
- <%= render(ModalComponent, user: {name: 'Jane'}) do |component| %>
214
+ <%= render(ModalComponent.new(user: {name: 'Jane'})) do |component| %>
232
215
  <% component.with(:header) do %>
233
216
  Hello <%= user[:name] %>
234
217
  <% end %>
@@ -266,7 +249,7 @@ end
266
249
  ```
267
250
 
268
251
  ```erb
269
- <%= render(ModalComponent, header: "Hi!") do |component| %>
252
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
270
253
  <% help_enabled? && component.with(:header) do %>
271
254
  <span class="help_icon"><%= component.header %></span>
272
255
  <% end %>
@@ -293,7 +276,7 @@ end
293
276
 
294
277
  `app/views/render_arg.html.erb`:
295
278
  ```erb
296
- <%= render(ModalComponent, header: "Hi!") do |component| %>
279
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
297
280
  <% component.with(:body) do %>
298
281
  <p>Have a great day.</p>
299
282
  <% end %>
@@ -304,7 +287,7 @@ end
304
287
  ```erb
305
288
  <%= render(ModalComponent) do |component| %>
306
289
  <% component.with(:header) do %>
307
- <span class="help_icon">Hello</span>
290
+ <span class="help_icon">Hello</span>
308
291
  <% end %>
309
292
  <% component.with(:body) do %>
310
293
  <p>Have a great day.</p>
@@ -339,7 +322,7 @@ end
339
322
 
340
323
  `app/views/render_arg.html.erb`:
341
324
  ```erb
342
- <%= render(ModalComponent, header: "Hi!") do |component| %>
325
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
343
326
  <% component.with(:body) do %>
344
327
  <p>Have a great day.</p>
345
328
  <% end %>
@@ -348,9 +331,9 @@ end
348
331
 
349
332
  `app/views/with_block.html.erb`:
350
333
  ```erb
351
- <%= render(ModalComponent) do |component| %>
334
+ <%= render(ModalComponent.new) do |component| %>
352
335
  <% component.with(:header) do %>
353
- <span class="help_icon">Hello</span>
336
+ <span class="help_icon">Hello</span>
354
337
  <% end %>
355
338
  <% component.with(:body) do %>
356
339
  <p>Have a great day.</p>
@@ -360,26 +343,76 @@ end
360
343
 
361
344
  `app/views/no_header.html.erb`:
362
345
  ```erb
363
- <%= render(ModalComponent) do |component| %>
346
+ <%= render(ModalComponent.new) do |component| %>
364
347
  <% component.with(:body) do %>
365
348
  <p>Have a great day.</p>
366
349
  <% end %>
367
350
  <% end %>
368
351
  ```
369
352
 
353
+ ### Conditional Rendering
354
+
355
+ Components can implement a `#render?` method which indicates if they should be rendered, or not at all.
356
+
357
+ For example, you might have a component that displays a "Please confirm your email address" banner to users who haven't confirmed their email address. The logic for rendering the banner would need to go in either the component template:
358
+
359
+ ```
360
+ <!-- app/components/confirm_email_component.html.erb -->
361
+ <% if user.requires_confirmation? %>
362
+ <div class="alert">
363
+ Please confirm your email address.
364
+ </div>
365
+ <% end %>
366
+ ```
367
+
368
+ or the view that renders the component:
369
+
370
+ ```erb
371
+ <!-- app/views/_banners.html.erb -->
372
+ <% if current_user.requires_confirmation? %>
373
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
374
+ <% end %>
375
+ ```
376
+
377
+ The `#render?` hook allows you to move this logic into the Ruby class, leaving your views more readable and declarative in style:
378
+
379
+ ```ruby
380
+ # app/components/confirm_email_component.rb
381
+ class ConfirmEmailComponent < ApplicationComponent
382
+ def initialize(user:)
383
+ @user = user
384
+ end
385
+
386
+ def render?
387
+ @user.requires_confirmation?
388
+ end
389
+ end
390
+ ```
391
+
392
+ ```
393
+ <!-- app/components/confirm_email_component.html.erb -->
394
+ <div class="banner">
395
+ Please confirm your email address.
396
+ </div>
397
+ ```
398
+
399
+ ```erb
400
+ <!-- app/views/_banners.html.erb -->
401
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
402
+ ```
403
+
370
404
  ### Testing
371
405
 
372
- Components are unit tested directly. The `render_inline` test helper wraps the result in `Nokogiri.HTML`, allowing us to test the component above as:
406
+ Components are unit tested directly. The `render_inline` test helper is compatible with Capybara matchers, allowing us to test the component above as:
373
407
 
374
408
  ```ruby
375
409
  require "action_view/component/test_case"
376
410
 
377
411
  class MyComponentTest < ActionView::Component::TestCase
378
412
  test "render component" do
379
- assert_equal(
380
- %(<span title="my title">Hello, World!</span>),
381
- render_inline(TestComponent, title: "my title") { "Hello, World!" }.to_html
382
- )
413
+ render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
414
+
415
+ assert_selector("span[title='my title']", "Hello, World!")
383
416
  end
384
417
  end
385
418
  ```
@@ -393,10 +426,9 @@ To test a specific variant you can wrap your test with the `with_variant` helper
393
426
  ```ruby
394
427
  test "render component for tablet" do
395
428
  with_variant :tablet do
396
- assert_equal(
397
- %(<span title="my title">Hello, tablets!</span>),
398
- render_inline(TestComponent, title: "my title") { "Hello, tablets!" }.css("span").to_html
399
- )
429
+ render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
430
+
431
+ assert_selector("span[title='my title']", "Hello, tablets!")
400
432
  end
401
433
  end
402
434
  ```
@@ -412,11 +444,11 @@ You can define as many examples as you want:
412
444
 
413
445
  class TestComponentPreview < ActionView::Component::Preview
414
446
  def with_default_title
415
- render(TestComponent, title: "Test component default")
447
+ render(TestComponent.new(title: "Test component default"))
416
448
  end
417
449
 
418
450
  def with_long_title
419
- render(TestComponent, title: "This is a really long title to see how the component renders this")
451
+ render(TestComponent.new(title: "This is a really long title to see how the component renders this"))
420
452
  end
421
453
  end
422
454
  ```
@@ -444,6 +476,16 @@ For example, if you want to use `lib/component_previews`, set the following in `
444
476
  config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
445
477
  ```
446
478
 
479
+ #### Configuring TestController
480
+
481
+ By default components tests and previews expect your Rails project to contain an `ApplicationController` class from which Controller classes inherit.
482
+ This can be configured using the `test_controller` option.
483
+ For example, if your controllers inherit from `BaseController`, set the following in `config/application.rb`:
484
+
485
+ ```ruby
486
+ config.action_view_component.test_controller = "BaseController"
487
+ ```
488
+
447
489
  ### Setting up RSpec
448
490
 
449
491
  If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
@@ -468,6 +510,11 @@ To use component previews, set the following in `config/application.rb`:
468
510
  config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
469
511
  ```
470
512
 
513
+ ### Initializer requirement
514
+
515
+ In Ruby 2.6.x and below, ActionView::Component requires the presence of an `initialize` method in each component.
516
+ However, `initialize` is no longer required for projects using 2.7.x and above.
517
+
471
518
  ## Frequently Asked Questions
472
519
 
473
520
  ### Can I use other templating languages besides ERB?
@@ -499,6 +546,35 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
499
546
 
500
547
  Bug reports and pull requests are welcome on GitHub at https://github.com/github/actionview-component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
501
548
 
549
+ ## Contributors
550
+
551
+ `actionview-component` is built by:
552
+
553
+ |<img src="https://avatars.githubusercontent.com/joelhawksley?s=256" alt="joelhawksley" width="128" />|<img src="https://avatars.githubusercontent.com/tenderlove?s=256" alt="tenderlove" width="128" />|<img src="https://avatars.githubusercontent.com/jonspalmer?s=256" alt="jonspalmer" width="128" />|<img src="https://avatars.githubusercontent.com/juanmanuelramallo?s=256" alt="juanmanuelramallo" width="128" />|<img src="https://avatars.githubusercontent.com/vinistock?s=256" alt="vinistock" width="128" />|
554
+ |:---:|:---:|:---:|:---:|:---:|
555
+ |@joelhawksley|@tenderlove|@jonspalmer|@juanmanuelramallo|@vinistock|
556
+ |Denver|Seattle|Boston||Toronto|
557
+
558
+ |<img src="https://avatars.githubusercontent.com/metade?s=256" alt="metade" width="128" />|<img src="https://avatars.githubusercontent.com/asgerb?s=256" alt="asgerb" width="128" />|<img src="https://avatars.githubusercontent.com/xronos-i-am?s=256" alt="xronos-i-am" width="128" />|<img src="https://avatars.githubusercontent.com/dylnclrk?s=256" alt="dylnclrk" width="128" />|<img src="https://avatars.githubusercontent.com/kaspermeyer?s=256" alt="kaspermeyer" width="128" />|
559
+ |:---:|:---:|:---:|:---:|:---:|
560
+ |@metade|@asgerb|@xronos-i-am|@dylnclrk|@kaspermeyer|
561
+ |London|Copenhagen|Russia, Kirov|Berkeley, CA|Denmark|
562
+
563
+ |<img src="https://avatars.githubusercontent.com/rdavid1099?s=256" alt="rdavid1099" width="128" />|<img src="https://avatars.githubusercontent.com/kylefox?s=256" alt="kylefox" width="128" />|<img src="https://avatars.githubusercontent.com/traels?s=256" alt="traels" width="128" />|<img src="https://avatars.githubusercontent.com/rainerborene?s=256" alt="rainerborene" width="128" />|<img src="https://avatars.githubusercontent.com/jcoyne?s=256" alt="jcoyne" width="128" />|
564
+ |:---:|:---:|:---:|:---:|:---:|
565
+ |@rdavid1099|@kylefox|@traels|@rainerborene|@jcoyne|
566
+ |Los Angeles|Edmonton|Odense, Denmark|Brazil|Minneapolis|
567
+
568
+ |<img src="https://avatars.githubusercontent.com/elia?s=256" alt="elia" width="128" />|<img src="https://avatars.githubusercontent.com/cesariouy?s=256" alt="cesariouy" width="128" />|<img src="https://avatars.githubusercontent.com/spdawson?s=256" alt="spdawson" width="128" />|<img src="https://avatars.githubusercontent.com/rmacklin?s=256" alt="rmacklin" width="128" />|<img src="https://avatars.githubusercontent.com/michaelem?s=256" alt="michaelem" width="128" />|
569
+ |:---:|:---:|:---:|:---:|:---:|
570
+ |@elia|@cesariouy|@spdawson|@rmacklin|@michaelem|
571
+ |Milan||United Kingdom||Berlin|
572
+
573
+ |<img src="https://avatars.githubusercontent.com/mellowfish?s=256" alt="mellowfish" width="128" />|<img src="https://avatars.githubusercontent.com/horacio?s=256" alt="horacio" width="128" />|<img src="https://avatars.githubusercontent.com/dukex?s=256" alt="dukex" width="128" />|<img src="https://avatars.githubusercontent.com/dark-panda?s=256" alt="dark-panda" width="128" />|<img src="https://avatars.githubusercontent.com/smashwilson?s=256" alt="smashwilson" width="128" />|
574
+ |:---:|:---:|:---:|:---:|:---:|
575
+ |@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
576
+ |Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
577
+
502
578
  ## License
503
579
 
504
580
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.required_ruby_version = ">= 2.3.0"
36
36
 
37
+ spec.add_runtime_dependency "capybara", ">= 3.26"
37
38
  spec.add_development_dependency "bundler", ">= 1.14"
38
39
  spec.add_development_dependency "rake", "~> 10.0"
39
40
  spec.add_development_dependency "minitest", "= 5.1.0"
@@ -47,6 +47,7 @@ module ActionView
47
47
  @view_flow ||= view_context.view_flow
48
48
  @virtual_path ||= virtual_path
49
49
  @variant = @lookup_context.variants.first
50
+
50
51
  old_current_template = @current_template
51
52
  @current_template = self
52
53
 
@@ -54,11 +55,17 @@ module ActionView
54
55
 
55
56
  validate!
56
57
 
58
+ return "" unless render?
59
+
57
60
  send(self.class.call_method_name(@variant))
58
61
  ensure
59
62
  @current_template = old_current_template
60
63
  end
61
64
 
65
+ def render?
66
+ true
67
+ end
68
+
62
69
  def initialize(*); end
63
70
 
64
71
  def render(options = {}, args = {}, &block)
@@ -112,6 +119,12 @@ module ActionView
112
119
 
113
120
  attr_reader :content, :view_context
114
121
 
122
+ # The controller used for testing components.
123
+ # Defaults to ApplicationController. This should be set early
124
+ # in the initialization process and should be set to a string.
125
+ mattr_accessor :test_controller
126
+ @@test_controller = "ApplicationController"
127
+
115
128
  class << self
116
129
  def inherited(child)
117
130
  child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
@@ -129,15 +142,11 @@ module ActionView
129
142
 
130
143
  def source_location
131
144
  @source_location ||=
132
- begin
133
- # Require #initialize to be defined so that we can use
134
- # method#source_location to look up the file name
135
- # of the component.
136
- #
137
- # If we were able to only support Ruby 2.7+,
138
- # We could just use Module#const_source_location,
139
- # rendering this unnecessary.
140
- #
145
+ if const_source_location_supported?
146
+ const_source_location(self.name)[0]
147
+ else
148
+ # Require `#initialize` to be defined so that we can use `method#source_location`
149
+ # to look up the filename of the component.
141
150
  initialize_method = instance_method(:initialize)
142
151
  initialize_method.source_location[0] if initialize_method.owner == self
143
152
  end
@@ -197,9 +206,13 @@ module ActionView
197
206
 
198
207
  private
199
208
 
209
+ def const_source_location_supported?
210
+ respond_to? :const_source_location # introduced in Ruby 2.7
211
+ end
212
+
200
213
  def matching_views_in_source_location
201
214
  return [] unless source_location
202
- (Dir["#{source_location.sub(/#{File.extname(source_location)}$/, '')}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
215
+ (Dir["#{source_location.chomp(File.extname(source_location))}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
203
216
  end
204
217
 
205
218
  def templates
@@ -219,7 +232,12 @@ module ActionView
219
232
  @template_errors ||=
220
233
  begin
221
234
  errors = []
222
- errors << "#{self} must implement #initialize." if source_location.nil?
235
+ if source_location.nil? && !const_source_location_supported?
236
+ # Require `#initialize` to be defined so that we can use `method#source_location`
237
+ # to look up the filename of the component.
238
+ errors << "#{self} must implement #initialize."
239
+ end
240
+
223
241
  errors << "Could not find a template file for #{self}." if templates.empty?
224
242
 
225
243
  if templates.count { |template| template[:variant].nil? } > 1
@@ -6,10 +6,9 @@ module ActionView
6
6
  module Component # :nodoc:
7
7
  class Preview
8
8
  extend ActiveSupport::DescendantsTracker
9
- include ActionView::Component::TestHelpers
10
9
 
11
- def render(component, *locals, &block)
12
- render_inline(component, *locals, &block)
10
+ def render(component, **args, &block)
11
+ { component: component, args: args, block: block }
13
12
  end
14
13
 
15
14
  class << self
@@ -19,21 +18,14 @@ module ActionView
19
18
  descendants
20
19
  end
21
20
 
22
- # Returns the html of the component in its layout
23
- def call(example, layout: nil)
24
- example_html = new.public_send(example)
25
- if layout.nil?
26
- layout = @layout.nil? ? "layouts/application" : @layout
27
- end
28
-
29
- Rails::ComponentExamplesController.render(template: "examples/show",
30
- layout: layout,
31
- assigns: { example: example_html })
21
+ # Returns the arguments for rendering of the component in its layout
22
+ def render_args(example)
23
+ new.public_send(example).merge(layout: @layout)
32
24
  end
33
25
 
34
26
  # Returns the component object class associated to the preview.
35
27
  def component
36
- self.name.sub(%r{Preview$}, "").constantize
28
+ name.chomp("Preview").constantize
37
29
  end
38
30
 
39
31
  # Returns all of the available examples for the component preview.
@@ -58,7 +50,7 @@ module ActionView
58
50
 
59
51
  # Returns the underscored name of the component preview without the suffix.
60
52
  def preview_name
61
- name.sub(/Preview$/, "").underscore
53
+ name.chomp("Preview").underscore
62
54
  end
63
55
 
64
56
  # Setter for layout name.
@@ -24,7 +24,6 @@ module ActionView
24
24
 
25
25
  initializer "action_view_component.set_autoload_paths" do |app|
26
26
  require "railties/lib/rails/components_controller"
27
- require "railties/lib/rails/component_examples_controller"
28
27
 
29
28
  options = app.config.action_view_component
30
29
 
@@ -1,24 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Monkey patch ActionView::Base#render to support ActionView::Component
4
- #
5
- # A version of this monkey patch was upstreamed in https://github.com/rails/rails/pull/36388
6
- # We'll need to upstream an updated version of this eventually.
7
4
  module ActionView
8
5
  module Component
9
6
  module RenderMonkeyPatch # :nodoc:
10
7
  def render(options = {}, args = {}, &block)
11
8
  if options.respond_to?(:render_in)
9
+ options.render_in(self, &block)
10
+ elsif options.is_a?(Class) && options < ActionView::Component::Base
12
11
  ActiveSupport::Deprecation.warn(
13
- "passing component instances (`render MyComponent.new(foo: :bar)`) has been deprecated and will be removed in v2.0.0. Use `render MyComponent, foo: :bar` instead."
12
+ "`render MyComponent, foo: :bar` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
14
13
  )
15
14
 
16
- options.render_in(self, &block)
17
- elsif options.is_a?(Class) && options < ActionView::Component::Base
18
15
  options.new(args).render_in(self, &block)
19
16
  elsif options.is_a?(Hash) && options.has_key?(:component)
17
+ ActiveSupport::Deprecation.warn(
18
+ "`render component: MyComponent, locals: { foo: :bar }` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
19
+ )
20
+
20
21
  options[:component].new(options[:locals]).render_in(self, &block)
21
22
  elsif options.respond_to?(:to_component_class) && !options.to_component_class.nil?
23
+ ActiveSupport::Deprecation.warn(
24
+ "rendering objects that respond_to `to_component_class` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
25
+ )
26
+
22
27
  options.to_component_class.new(options).render_in(self, &block)
23
28
  else
24
29
  super
@@ -1,14 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "capybara/minitest"
4
+
3
5
  module ActionView
4
6
  module Component
5
7
  module TestHelpers
8
+ include Capybara::Minitest::Assertions
9
+
10
+ def page
11
+ Capybara::Node::Simple.new(@raw)
12
+ end
13
+
6
14
  def render_inline(component, **args, &block)
7
- Nokogiri::HTML.fragment(controller.view_context.render(component, args, &block))
15
+ @raw = controller.view_context.render(component, args, &block)
16
+
17
+ Nokogiri::HTML.fragment(@raw)
8
18
  end
9
19
 
10
20
  def controller
11
- @controller ||= ApplicationController.new.tap { |c| c.request = request }
21
+ @controller ||= Base.test_controller.constantize.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
12
22
  end
13
23
 
14
24
  def request
@@ -4,7 +4,7 @@ module ActionView
4
4
  module Component
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 7
7
+ MINOR = 11
8
8
  PATCH = 0
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -6,15 +6,18 @@ module Rails
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
8
  argument :attributes, type: :array, default: [], banner: "attribute"
9
- hook_for :test_framework
10
9
  check_class_collision suffix: "Component"
11
10
 
11
+ class_option :require_content, type: :boolean, default: false
12
+
12
13
  def create_component_file
13
- template "component.rb", File.join("app/components", "#{file_name}_component.rb")
14
+ template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
14
15
  end
15
16
 
16
- def create_template_file
17
- template "component.html.erb", File.join("app/components", "#{file_name}_component.html.erb")
17
+ hook_for :test_framework
18
+
19
+ hook_for :template_engine do |instance, template_engine|
20
+ instance.invoke template_engine, [instance.name], require_content: instance.send(:requires_content?)
18
21
  end
19
22
 
20
23
  private
@@ -24,6 +27,7 @@ module Rails
24
27
  end
25
28
 
26
29
  def requires_content?
30
+ return if behavior == :revoke
27
31
  return @requires_content if @asked
28
32
 
29
33
  @asked = true
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb"
4
+
5
+ module Erb
6
+ module Generators
7
+ class ComponentGenerator < Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb/component_generator"
4
+
5
+ module Haml
6
+ module Generators
7
+ class ComponentGenerator < Erb::Generators::ComponentGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.haml", File.join("app/components", class_path, "#{file_name}_component.html.haml")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ <%- if requires_content? -%>
2
+ <%= "= content" %>
3
+ <%- else -%>
4
+ %div Add <%= class_name %> template here
5
+ <%- end -%>
@@ -6,7 +6,7 @@ module Rspec
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
8
  def create_test_file
9
- template "component_spec.rb", File.join("spec/components", "#{file_name}_component_spec.rb")
9
+ template "component_spec.rb", File.join("spec/components", class_path, "#{file_name}_component_spec.rb")
10
10
  end
11
11
 
12
12
  private
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb/component_generator"
4
+
5
+ module Slim
6
+ module Generators
7
+ class ComponentGenerator < Erb::Generators::ComponentGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.slim", File.join("app/components", class_path, "#{file_name}_component.html.slim")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ <%- if requires_content? -%>
2
+ = content
3
+ <%- else -%>
4
+ div Add <%= class_name %> template here
5
+ <%- end -%>
@@ -7,7 +7,7 @@ module TestUnit
7
7
  check_class_collision suffix: "ComponentTest"
8
8
 
9
9
  def create_test_file
10
- template "component_test.rb", File.join("test/components", "#{file_name}_component_test.rb")
10
+ template "component_test.rb", File.join("test/components", class_path, "#{file_name}_component_test.rb")
11
11
  end
12
12
 
13
13
  private
@@ -2,5 +2,4 @@
2
2
 
3
3
  module Rails
4
4
  autoload :ComponentsController
5
- autoload :ComponentExamplesController
6
5
  end
@@ -4,6 +4,7 @@ require "rails/application_controller"
4
4
 
5
5
  class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
6
6
  prepend_view_path File.expand_path("templates/rails", __dir__)
7
+ prepend_view_path "#{Rails.root}/app/views/" if defined?(Rails.root)
7
8
 
8
9
  around_action :set_locale, only: :previews
9
10
  before_action :find_preview, only: :previews
@@ -25,7 +26,10 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
25
26
  render template: "components/previews"
26
27
  else
27
28
  @example_name = File.basename(params[:path])
28
- render template: "components/preview", layout: false
29
+ @render_args = @preview.render_args(@example_name)
30
+ layout = @render_args[:layout]
31
+ opts = layout.nil? ? {} : { layout: layout }
32
+ render template: "components/preview", **opts
29
33
  end
30
34
  end
31
35
 
@@ -1 +1 @@
1
- <%= raw @preview.call(@example_name) %>
1
+ <%= render(@render_args[:component], **@render_args[:args], &@render_args[:block])%>
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionview-component
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-09 00:00:00.000000000 Z
11
+ date: 2020-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.26'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.26'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -156,19 +170,22 @@ files:
156
170
  - lib/action_view/component/version.rb
157
171
  - lib/rails/generators/component/USAGE
158
172
  - lib/rails/generators/component/component_generator.rb
159
- - lib/rails/generators/component/templates/component.html.erb.tt
160
173
  - lib/rails/generators/component/templates/component.rb.tt
174
+ - lib/rails/generators/erb/component_generator.rb
175
+ - lib/rails/generators/erb/templates/component.html.erb.tt
176
+ - lib/rails/generators/haml/component_generator.rb
177
+ - lib/rails/generators/haml/templates/component.html.haml.tt
161
178
  - lib/rails/generators/rspec/component_generator.rb
162
179
  - lib/rails/generators/rspec/templates/component_spec.rb.tt
180
+ - lib/rails/generators/slim/component_generator.rb
181
+ - lib/rails/generators/slim/templates/component.html.slim
163
182
  - lib/rails/generators/test_unit/component_generator.rb
164
183
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
165
184
  - lib/railties/lib/rails.rb
166
- - lib/railties/lib/rails/component_examples_controller.rb
167
185
  - lib/railties/lib/rails/components_controller.rb
168
186
  - lib/railties/lib/rails/templates/rails/components/index.html.erb
169
187
  - lib/railties/lib/rails/templates/rails/components/preview.html.erb
170
188
  - lib/railties/lib/rails/templates/rails/components/previews.html.erb
171
- - lib/railties/lib/rails/templates/rails/examples/show.html.erb
172
189
  - script/bootstrap
173
190
  - script/console
174
191
  - script/install
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/application_controller"
4
- load "config/application.rb" unless Rails.root
5
-
6
- class Rails::ComponentExamplesController < ActionController::Base # :nodoc:
7
- prepend_view_path File.expand_path("templates/rails", __dir__)
8
- append_view_path Rails.root.join("app/views")
9
- end
@@ -1 +0,0 @@
1
- <%= raw @example %>