view_component 4.0.0 → 4.12.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.
@@ -21,11 +21,39 @@ module ViewComponent
21
21
 
22
22
  class File < Template
23
23
  def initialize(component:, details:, path:)
24
+ # If the template file has no format (e.g. .erb instead of .html.erb),
25
+ # assume the default format (html).
26
+ if details.format.nil?
27
+ Kernel.warn("WARNING: Template format for #{path} is missing, defaulting to :html.")
28
+ details = ActionView::TemplateDetails.new(details.locale, details.handler, DEFAULT_FORMAT, details.variant)
29
+ end
30
+
31
+ @strip_annotation_line = false
32
+
33
+ # Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
34
+ # Use -1 to compensate for correct line numbers in stack traces.
35
+ # However, negative line numbers cause segfaults when Ruby's coverage
36
+ # is enabled (bugs.ruby-lang.org/issues/19363). In that case, strip the
37
+ # annotation line from compiled source instead.
38
+ lineno =
39
+ if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
40
+ if coverage_running?
41
+ # Can't use negative lineno with coverage (causes segfault on Linux).
42
+ # Strip annotation line if enabled to preserve correct line numbers.
43
+ @strip_annotation_line = ActionView::Base.annotate_rendered_view_with_filenames
44
+ 0
45
+ else
46
+ -1
47
+ end
48
+ else
49
+ 0
50
+ end
51
+
24
52
  super(
25
53
  component: component,
26
54
  details: details,
27
55
  path: path,
28
- lineno: 0
56
+ lineno: lineno
29
57
  )
30
58
  end
31
59
 
@@ -37,6 +65,16 @@ module ViewComponent
37
65
  def source
38
66
  ::File.read(@path)
39
67
  end
68
+
69
+ private
70
+
71
+ def compiled_source
72
+ result = super
73
+ # Strip the annotation line to maintain correct line numbers when coverage
74
+ # is running (avoids segfault from negative lineno)
75
+ result = result.partition(";").last if @strip_annotation_line
76
+ result
77
+ end
40
78
  end
41
79
 
42
80
  class Inline < Template
@@ -45,11 +83,22 @@ module ViewComponent
45
83
  def initialize(component:, inline_template:)
46
84
  details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, nil, nil)
47
85
 
86
+ # Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
87
+ # Subtract 1 to compensate for correct line numbers in stack traces.
88
+ # Inline templates start at line 2+ (defined inside a class), so this
89
+ # won't result in negative line numbers that cause segfaults with coverage.
90
+ lineno =
91
+ if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
92
+ inline_template.lineno - 1
93
+ else
94
+ inline_template.lineno
95
+ end
96
+
48
97
  super(
49
98
  component: component,
50
99
  details: details,
51
100
  path: inline_template.path,
52
- lineno: inline_template.lineno,
101
+ lineno: lineno,
53
102
  )
54
103
 
55
104
  @source = inline_template.source.dup
@@ -108,6 +157,10 @@ module ViewComponent
108
157
  @component.define_method(safe_method_name, @component.instance_method(@call_method_name))
109
158
  end
110
159
 
160
+ def coverage_running?
161
+ defined?(Coverage) && Coverage.running?
162
+ end
163
+
111
164
  def safe_method_name_call
112
165
  m = safe_method_name
113
166
  proc { send(m) }
@@ -35,12 +35,23 @@ module ViewComponent
35
35
  # ```
36
36
  #
37
37
  # @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered.
38
+ #
38
39
  # @return [Nokogiri::HTML5]
39
40
  def render_inline(component, **args, &block)
40
41
  @page = nil
41
- @rendered_content = vc_test_controller.view_context.render(component, args, &block)
42
+ @rendered_content = vc_test_view_context.render(component, args, &block)
42
43
 
43
- Nokogiri::HTML5.fragment(@rendered_content)
44
+ fragment = Nokogiri::HTML5.fragment(@rendered_content, context: "template")
45
+ @vc_test_view_context = nil
46
+ fragment
47
+ end
48
+
49
+ # Returns the view context used to render components in tests. Note that the view context
50
+ # is reset after each call to `render_inline`.
51
+ #
52
+ # @return [ActionView::Base]
53
+ def vc_test_view_context
54
+ @vc_test_view_context ||= vc_test_controller.view_context
44
55
  end
45
56
 
46
57
  # `JSON.parse`-d component output.
@@ -61,17 +72,18 @@ module ViewComponent
61
72
  # assert_text("Hello, World!")
62
73
  # ```
63
74
  #
64
- # Note: `#rendered_preview` expects a preview to be defined with the same class
65
- # name as the calling test, but with `Test` replaced with `Preview`:
66
- #
67
- # MyComponentTest -> MyComponentPreview etc.
68
- #
69
- # In RSpec, `Preview` is appended to `described_class`.
70
- #
71
75
  # @param name [String] The name of the preview to be rendered.
72
76
  # @param from [ViewComponent::Preview] The class of the preview to be rendered.
73
77
  # @param params [Hash] Parameters to be passed to the preview.
78
+ #
74
79
  # @return [Nokogiri::HTML5]
80
+ #
81
+ # @note `#rendered_preview` expects a preview to be defined with the same class
82
+ # name as the calling test, but with `Test` replaced with `Preview`:
83
+ #
84
+ # MyComponentTest -> MyComponentPreview etc.
85
+ #
86
+ # In RSpec, `Preview` is appended to `described_class`.
75
87
  def render_preview(name, from: __vc_test_helpers_preview_class, params: {})
76
88
  previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.previews.controller.constantize)
77
89
 
@@ -103,7 +115,7 @@ module ViewComponent
103
115
  # ```
104
116
  def render_in_view_context(...)
105
117
  @page = nil
106
- @rendered_content = vc_test_controller.view_context.instance_exec(...)
118
+ @rendered_content = vc_test_view_context.instance_exec(...)
107
119
  Nokogiri::HTML5.fragment(@rendered_content)
108
120
  end
109
121
 
@@ -115,7 +127,7 @@ module ViewComponent
115
127
  # end
116
128
  # ```
117
129
  #
118
- # @param variants [Symbol[]] The variants to be set for the provided block.
130
+ # @param variants [Array<Symbol>] The variants to be set for the provided block.
119
131
  def with_variant(*variants)
120
132
  old_variants = vc_test_controller.view_context.lookup_context.variants
121
133
 
@@ -152,7 +164,7 @@ module ViewComponent
152
164
  # end
153
165
  # ```
154
166
  #
155
- # @param formats [Symbol[]] The format(s) to be set for the provided block.
167
+ # @param formats [Array<Symbol>] The format(s) to be set for the provided block.
156
168
  def with_format(*formats)
157
169
  old_formats = vc_test_controller.view_context.lookup_context.formats
158
170
 
@@ -186,10 +198,19 @@ module ViewComponent
186
198
  # end
187
199
  # ```
188
200
  #
201
+ # To specify a protocol, pass the protocol param:
202
+ #
203
+ # ```ruby
204
+ # with_request_url("/users/42", protocol: :https) do
205
+ # render_inline(MyComponent.new)
206
+ # end
207
+ # ```
208
+ #
189
209
  # @param full_path [String] The path to set for the current request.
190
210
  # @param host [String] The host to set for the current request.
191
211
  # @param method [String] The request method to set for the current request.
192
- def with_request_url(full_path, host: nil, method: nil)
212
+ # @param protocol [Symbol] The protocol to set for the current request (e.g., `:http` or `:https`).
213
+ def with_request_url(full_path, host: nil, method: nil, protocol: nil)
193
214
  old_request_host = vc_test_request.host
194
215
  old_request_method = vc_test_request.request_method
195
216
  old_request_path_info = vc_test_request.path_info
@@ -197,6 +218,7 @@ module ViewComponent
197
218
  old_request_query_parameters = vc_test_request.query_parameters
198
219
  old_request_query_string = vc_test_request.query_string
199
220
  old_request_format = vc_test_request.format.symbol
221
+ old_request_scheme = vc_test_request.scheme
200
222
  old_controller = defined?(@vc_test_controller) && @vc_test_controller
201
223
 
202
224
  path, query = full_path.split("?", 2)
@@ -204,6 +226,7 @@ module ViewComponent
204
226
  vc_test_request.instance_variable_set(:@original_fullpath, full_path)
205
227
  vc_test_request.host = host if host
206
228
  vc_test_request.request_method = method if method
229
+ vc_test_request.set_header(Rack::RACK_URL_SCHEME, protocol.to_s) if protocol
207
230
  vc_test_request.path_info = path
208
231
  vc_test_request.path_parameters = Rails.application.routes.recognize_path_with_request(vc_test_request, path, {})
209
232
  vc_test_request.set_header("action_dispatch.request.query_parameters",
@@ -213,6 +236,7 @@ module ViewComponent
213
236
  ensure
214
237
  vc_test_request.host = old_request_host
215
238
  vc_test_request.request_method = old_request_method
239
+ vc_test_request.set_header(Rack::RACK_URL_SCHEME, old_request_scheme)
216
240
  vc_test_request.path_info = old_request_path_info
217
241
  vc_test_request.path_parameters = old_request_path_parameters
218
242
  vc_test_request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 4
6
- MINOR = 0
6
+ MINOR = 12
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "action_view"
4
4
  require "active_support/dependencies/autoload"
5
+ require "view_component/version"
5
6
 
6
7
  module ViewComponent
7
8
  extend ActiveSupport::Autoload
@@ -16,7 +17,7 @@ module ViewComponent
16
17
  autoload :Preview
17
18
  autoload :Translatable
18
19
 
19
- if Rails.env.test?
20
+ if defined?(Rails.env) && Rails.env.test?
20
21
  autoload :TestHelpers
21
22
  autoload :SystemSpecHelpers
22
23
  autoload :SystemTestHelpers
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
@@ -16,9 +16,6 @@ dependencies:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: 7.1.0
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '8.1'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,9 +23,20 @@ dependencies:
26
23
  - - ">="
27
24
  - !ruby/object:Gem::Version
28
25
  version: 7.1.0
29
- - - "<"
26
+ - !ruby/object:Gem::Dependency
27
+ name: actionview
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
30
31
  - !ruby/object:Gem::Version
31
- version: '8.1'
32
+ version: 7.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 7.1.0
32
40
  - !ruby/object:Gem::Dependency
33
41
  name: concurrent-ruby
34
42
  requirement: !ruby/object:Gem::Requirement
@@ -127,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
135
  - !ruby/object:Gem::Version
128
136
  version: '0'
129
137
  requirements: []
130
- rubygems_version: 3.6.8
138
+ rubygems_version: 4.0.10
131
139
  specification_version: 4
132
140
  summary: A framework for building reusable, testable & encapsulated view components
133
141
  in Ruby on Rails.