Skip to content

Commit 51cc863

Browse files
ryanclark2Sam Phippen
authored and
Sam Phippen
committed
Fix URL helpers via the extra_params API to view specs.
This enables URL helpers to work in view specs via the `extra_params` API. This API updates the controller's path information via a writer and allows the user to read path information out of the controller.
1 parent a6bdb46 commit 51cc863

File tree

5 files changed

+205
-9
lines changed

5 files changed

+205
-9
lines changed

features/view_specs/view_spec.feature

+40-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Feature: view spec
22

33
View specs live in spec/views and render view templates in isolation.
44

5-
Scenario: passing spec that renders the described view file
5+
Scenario: View specs render the described view file
66
Given a file named "spec/views/widgets/index.html.erb_spec.rb" with:
77
"""ruby
88
require "rails_helper"
@@ -24,7 +24,7 @@ Feature: view spec
2424
When I run `rspec spec/views`
2525
Then the examples should all pass
2626

27-
Scenario: passing spec with before and nesting
27+
Scenario: View specs can have before block and nesting
2828
Given a file named "spec/views/widgets/index.html.erb_spec.rb" with:
2929
"""ruby
3030
require "rails_helper"
@@ -51,7 +51,7 @@ Feature: view spec
5151
When I run `rspec spec/views`
5252
Then the examples should all pass
5353

54-
Scenario: passing spec with explicit template rendering
54+
Scenario: View specs can explicitly render templates
5555
Given a file named "spec/views/widgets/widget.html.erb_spec.rb" with:
5656
"""ruby
5757
require "rails_helper"
@@ -73,7 +73,7 @@ Feature: view spec
7373
When I run `rspec spec/views`
7474
Then the examples should all pass
7575

76-
Scenario: passing spec with a description that includes the format and handler
76+
Scenario: View specs can have description that includes the format and handler
7777
Given a file named "spec/views/widgets/widget.xml.erb_spec.rb" with:
7878
"""ruby
7979
require "rails_helper"
@@ -105,7 +105,7 @@ Feature: view spec
105105
When I run `rspec spec/views`
106106
Then the examples should all pass
107107

108-
Scenario: passing spec with rendering of locals in a partial
108+
Scenario: View specs can render locals in a partial
109109
Given a file named "spec/views/widgets/_widget.html.erb_spec.rb" with:
110110
"""ruby
111111
require "rails_helper"
@@ -127,7 +127,7 @@ Feature: view spec
127127
When I run `rspec spec/views`
128128
Then the examples should all pass
129129

130-
Scenario: passing spec with rendering of locals in an implicit partial
130+
Scenario: View specs can render locals in an implicit partial
131131
Given a file named "spec/views/widgets/_widget.html.erb_spec.rb" with:
132132
"""ruby
133133
require "rails_helper"
@@ -149,7 +149,7 @@ Feature: view spec
149149
When I run `rspec spec/views`
150150
Then the examples should all pass
151151

152-
Scenario: passing spec with rendering of text
152+
Scenario: View specs can render text
153153
Given a file named "spec/views/widgets/direct.html.erb_spec.rb" with:
154154
"""ruby
155155
require "rails_helper"
@@ -166,7 +166,7 @@ Feature: view spec
166166
When I run `rspec spec/views`
167167
Then the examples should all pass
168168

169-
Scenario: passing view spec that stubs a helper method
169+
Scenario: View specs can stub a helper method
170170
Given a file named "app/helpers/application_helper.rb" with:
171171
"""ruby
172172
module ApplicationHelper
@@ -199,7 +199,7 @@ Feature: view spec
199199
When I run `rspec spec/views/secrets`
200200
Then the examples should all pass
201201

202-
Scenario: request.path_parameters should match Rails by using symbols for keys
202+
Scenario: View specs use symbols for keys in `request.path_parameters` to match Rails style
203203
Given a file named "spec/views/widgets/index.html.erb_spec.rb" with:
204204
"""ruby
205205
require "rails_helper"
@@ -212,3 +212,34 @@ Feature: view spec
212212
"""
213213
When I run `rspec spec/views`
214214
Then the examples should all pass
215+
216+
Scenario: View spec actions that do not require extra parameters have `request.fullpath` set
217+
Given a file named "spec/views/widgets/index.html.erb_spec.rb" with:
218+
"""ruby
219+
require "rails_helper"
220+
221+
RSpec.describe "widgets/index" do
222+
it "has a request.fullpath that is defined" do
223+
expect(controller.request.fullpath).to eq widgets_path
224+
end
225+
end
226+
"""
227+
When I run `rspec spec/views`
228+
Then the examples should all pass
229+
230+
Scenario: View spec actions that require extra parameters have `request.fullpath` set when the developer supplies them
231+
Given a file named "spec/views/widgets/show.html.erb_spec.rb" with:
232+
"""ruby
233+
require "rails_helper"
234+
235+
RSpec.describe "widgets/show" do
236+
it "displays the widget with id: 1" do
237+
widget = Widget.create!(:name => "slicer")
238+
controller.extra_params = { :id => widget.id }
239+
240+
expect(controller.request.fullpath).to eq widget_path(widget)
241+
end
242+
end
243+
"""
244+
When I run `rspec spec/views`
245+
Then the examples should all pass

lib/rspec/rails/example/view_example_group.rb

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
require 'rspec/rails/view_assigns'
2+
require 'rspec/rails/view_spec_methods'
3+
require 'rspec/rails/view_path_builder'
24

35
module RSpec
46
module Rails
@@ -159,6 +161,12 @@ def _include_controller_helpers
159161
controller.controller_path = _controller_path
160162
controller.request.path_parameters[:controller] = _controller_path
161163
controller.request.path_parameters[:action] = _inferred_action unless _inferred_action =~ /^_/
164+
controller.request.path = ViewPathBuilder.new(::Rails.application.routes).path_for(controller.request.path_parameters)
165+
ViewSpecMethods.add_to(::ActionView::TestCase::TestController)
166+
end
167+
168+
after do
169+
ViewSpecMethods.remove_from(::ActionView::TestCase::TestController)
162170
end
163171

164172
let(:_default_file_to_render) do |example|

lib/rspec/rails/view_path_builder.rb

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module RSpec
2+
module Rails
3+
# Builds paths for view specs using a particular route set.
4+
class ViewPathBuilder
5+
def initialize(route_set)
6+
self.class.send(:include, route_set.url_helpers)
7+
end
8+
9+
# Given a hash of parameters, build a view path, if possible.
10+
# Returns nil if no path can be built from the given params.
11+
#
12+
# @example
13+
# # path can be built because all required params are present in the hash
14+
# view_path_builder = ViewPathBuilder.new(::Rails.application.routes)
15+
# view_path_builder.path_for({ :controller => 'posts', :action => 'show', :id => '54' })
16+
# # => "/post/54"
17+
#
18+
# @example
19+
# # path cannot be built because the params are missing a required element (:id)
20+
# view_path_builder.path_for({ :controller => 'posts', :action => 'delete' })
21+
# # => nil
22+
def path_for(path_params)
23+
url_for(path_params.merge(:only_path => true))
24+
rescue => e
25+
e.message
26+
end
27+
end
28+
end
29+
end

lib/rspec/rails/view_spec_methods.rb

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
module RSpec
2+
module Rails
3+
# Adds methods (generally to ActionView::TestCase::TestController).
4+
# Intended for use in view specs.
5+
module ViewSpecMethods
6+
module_function
7+
8+
# Adds methods `extra_params=` and `extra_params` to the indicated class.
9+
# When class is `::ActionView::TestCase::TestController`, these methods
10+
# are exposed in view specs on the `controller` object.
11+
def add_to(klass)
12+
return if klass.method_defined?(:extra_params) && klass.method_defined?(:extra_params=)
13+
14+
klass.module_exec do
15+
# Set any extra parameters that rendering a URL for this view
16+
# would require.
17+
#
18+
# @example
19+
#
20+
# # In "spec/views/widgets/show.html.erb_spec.rb":
21+
# before do
22+
# widget = Widget.create!(:name => "slicer")
23+
# controller.extra_params = { :id => widget.id }
24+
# end
25+
def extra_params=(hash)
26+
@extra_params = hash
27+
request.path =
28+
ViewPathBuilder.new(::Rails.application.routes).path_for(
29+
extra_params.merge(request.path_parameters)
30+
)
31+
end
32+
33+
# Use to read extra parameters that are set in the view spec.
34+
#
35+
# @example
36+
#
37+
# # After the before in the above example:
38+
# controller.extra_params
39+
# # => { :id => 4 }
40+
def extra_params
41+
@extra_params ||= {}
42+
@extra_params.dup.freeze
43+
end
44+
end
45+
end
46+
47+
# Removes methods `extra_params=` and `extra_params` from the indicated class.
48+
def remove_from(klass)
49+
klass.module_exec do
50+
undef extra_params= if klass.method_defined?(:extra_params=)
51+
undef extra_params if klass.method_defined?(:extra_params)
52+
end
53+
end
54+
end
55+
end
56+
end
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
module RSpec::Rails
2+
RSpec.describe ViewSpecMethods do
3+
before do
4+
class ::VCSampleClass; end
5+
end
6+
7+
after do
8+
Object.send(:remove_const, :VCSampleClass)
9+
end
10+
11+
describe ".add_extra_params_accessors_to" do
12+
describe "when accessors are not yet defined" do
13+
it "adds them as instance methods" do
14+
ViewSpecMethods.add_to(VCSampleClass)
15+
16+
expect(VCSampleClass.instance_methods.map(&:to_sym)).to(include(:extra_params=))
17+
expect(VCSampleClass.instance_methods.map(&:to_sym)).to(include(:extra_params))
18+
end
19+
20+
describe "the added #extra_params reader" do
21+
it "raises an error when a user tries to mutate it" do
22+
ViewSpecMethods.add_to(VCSampleClass)
23+
24+
expect {
25+
VCSampleClass.new.extra_params[:id] = 4
26+
}.to raise_error(/can't modify frozen/)
27+
end
28+
end
29+
end
30+
31+
describe "when accessors are already defined" do
32+
before do
33+
class ::VCSampleClass
34+
def extra_params; end
35+
36+
def extra_params=; end
37+
end
38+
end
39+
40+
it "does not redefine them" do
41+
ViewSpecMethods.add_to(VCSampleClass)
42+
expect(VCSampleClass.new.extra_params).to be_nil
43+
end
44+
end
45+
end
46+
47+
describe ".remove_extra_params_accessors_from" do
48+
describe "when accessors are defined" do
49+
before do
50+
ViewSpecMethods.add_to(VCSampleClass)
51+
end
52+
53+
it "removes them" do
54+
ViewSpecMethods.remove_from(VCSampleClass)
55+
56+
expect(VCSampleClass.instance_methods).to_not include("extra_params=")
57+
expect(VCSampleClass.instance_methods).to_not include(:extra_params=)
58+
expect(VCSampleClass.instance_methods).to_not include("extra_params")
59+
expect(VCSampleClass.instance_methods).to_not include(:extra_params)
60+
end
61+
end
62+
63+
describe "when accessors are not defined" do
64+
it "does nothing" do
65+
expect {
66+
ViewSpecMethods.remove_from(VCSampleClass)
67+
}.to_not change { VCSampleClass.instance_methods }
68+
end
69+
end
70+
end
71+
end
72+
end

0 commit comments

Comments
 (0)