Rspec view test does not render changes - ruby-on-rails

I am testing a view in my rails 3.2 application with rspec.
I have wrote tests for my view to include some additional input fields, and they correctly failed. However, after adding the desired input fields, the tests still fail the same way. They output the form in the terminal, and it is as if I hadn't changed anything in the views.
When inspecting the view in the browser, the fields are in fact there, so the tests should pass.
Has rspec not loaded the latest views?
Here is some code (I have reduced it to two fields):
it "renders the form to sign up" do
rendered.should have_selector("form", action: "/users", method: "post") do |form|
form.should have_selector("input#user_email", name: "user[email]", type: "email")
form.should have_selector("input#user_city", name: "user[city]", type: "text")
end
end
The email input is an old input that I had before, and it does recognize it. The city input is new, and it doesn't even appear in the terminal's view output.
What am I doing wrong?

Wow, this was a tricky one.
I am using devise and wanted to test its views. In my view tests, I said:
describe "devise/registrations/new" do
# code here
end
However, the other day I switched to scoped views, and changed the view folder name from devise to users accordingly. Scoped views are good if you have different user models such as user and admin and need different views for them. You can turn on scoped views in config/initializers/devise.rb by replacing the existing scope line with:
config.scoped_views = true
Hence, I also had to change my spec description to
describe "users/registrations/new" do # change to "users" instead of "devise"
# code here
end
Since I didn't do this, it still rendered devise's standard views, which it renders when it doesn't find any views in my views folder - hence the source code output in my terminal, that suggested it could render the right views, but lacked the updated code.

Related

Why Rails defaults creation to a POST on collection in HTML form?

When generating a scaffold, by default the new_resource_path generates a form that will submit to resources_path.
This makes total sense in a RESTful mentality.
But, given that the generated material does not uses it as a REST resource, why does it POST to the collection path?
When the resource is successfully created, Rails will redirect to the created resource path. When any error occurs, Rails will render the new template that will present the errors (generated by scaffolding).
This seems fine, except that when any errors occurs when trying to create the resource, the URL will change to the collection path. This means that if user tries to refresh the page, it will not see the creation form. If the application does not allow listing for this resource, a routing error may happen. In case the application uses any type of authorization and the current user does not has the required authorization to list stuff, it may see a forbidden.
I see Rails scaffold generator as something the community agrees to be the standard way to do basic CRUD in it. So, why this behavior?
It seems that by keeping a purist RESTful resources approach we are breaking user experience a bit.
To see an example of this, just create a new Rails application, scaffold a new entity and try to create it with some validation errors.
$ rails new example
$ cd example
$ rails generate scaffold note text
# edit app/models/note.rb
class Note < ApplicationRecord
validates :text, length: { minimum: 10 }
end
$ rails db:migrate
$ rails server
# go to localhost:3000/notes/new
# click 'Create Note'
# see the error
# hit browser's refresh button
# now you are listing notes, and not creating one
If you think "this should not harm a real application". I've come up with this when writing tests for authentication.
My application is using Devise and fails for this test:
test 'new user should not be able to register with wrong password confirmation' do
email = 'newuser#newdomain.com'
password = 'little$secret'
password_confirmation = 'big$secret'
visit new_user_registration_path
fill_in 'Email', with: email
fill_in 'Password', with: password
fill_in 'Password confirmation', with: password_confirmation
assert_no_difference ->{ User.count } do
click_on 'Sign up'
end
assert page.has_content?("Password confirmation doesn't match Password")
# FAILS:
assert_equal new_user_registration_path, current_path
end
What this means in real life: When user tries to create an account, submit an invalid form, see the error and hit refresh, it is on an invalid path as the resource does not support listing (i.e. /users).
To make that last assertion pass, I had to overwrite the default Devise view to submit the form to /users/sign_up instead of just /users and to add a new route to call create when a POST is made to this URL. Then I realized that this will happen to any controller following the RESTful Resource approach, unless developers create this new route and use a custom URL for submitting creation forms.
Also, the "purist RESTful Resource approach" doesn't seem to be so purist. When you submit your form with invalid data, the POST will result in a 200 OK rendering an HTML with errors, instead of a 400 Bad Request. So, why not submit the form to the same URL the form exists in?
My bet is that I'm missing something, but I can't figure it out. So, what am I missing?
But, given that the generated material does not uses it as a REST
resource, why does it POST to the collection path?
So, why not submit the form to the same URL the form exists in?
Because the rails conventions embrace statelessness.
The form that you see when a create fails shows the result of a POST request. It is not meant to be repeated - or shared.
You could potentially have POST /notes/create and create a GET /notes/create route so that it would show the form after a refresh - but is that a good design from a framework point of view? I would say no.
Forms that POST back to the same URL can give a bad user experience - like the "Confirm form submission" dialog when you hit the back button. This is actually worse than the scenario you are painting up as it can lead to unexpected consequences for the user.
I see Rails scaffold generator as something the community agrees to be
the standard way to do basic CRUD in it.
The rails scaffold command is a rapid prototyping tool. They are not meant as the authoritative source of the "right" way to do rails nor does the community hold them as the word of god.
Also, the "purist RESTful Resource approach" doesn't seem to be so
purist.
The Rails community is not very purist. If anything its quite pragmatic and aims towards embracing concepts like REST but with a focus on developer convenience and "should just work".
When you submit your form with invalid data, the POST will
result in a 200 OK rendering an HTML with errors, instead of a 400 Bad
Request.
This is pragmatism, back in the day Internet Explorer would do all kinds of annoying things when given 4XX response codes. 200 OK guarantees the client will render the response - although it is tecnically wrong.
This seems fine, except that when any errors occurs when trying to
create the resource, the URL will change to the collection path. This
means that if user tries to refresh the page, it will not see the
creation form.
I don't get you : If you refresh the page, it will just re-POST the same parameters and so show the same form with errors. I just re-checked that.
If the application does not allow listing for this resource, a routing
error may happen. In case the application uses any type of
authorization and the current user does not has the required
authorization to list stuff, it may see a forbidden.
So, a user would not be allowed, for example, to view a list of posts, but it would allowed to create a new one ?

Devise: logic using `admin_signed_in?` when Admin & User logged in on separate tabs in same browser

In my Rails 3.2 app there are two Devise models: User and Admin. I have a comment partial form that both can use to make comments on a Post. However, I have included conditional logic with the *_signed_in? helper provided by Devise so that a checkbox appears for admins that allows them to make their comment visible only to other admins. Form checkbox code:
- if admin_signed_in?
.pull-right
= label_tag :internal, "Private"
= f.check_box :internal
It's not a huge issue because it should never occur in production, but in development and staging I've noticed that if someone (tester, etc) is logged in as both an Admin and a User in different tabs on the same browser, the logic of my form doesn't work because (I guess?) those two tabs are using the same cookie/session info/whatever. The checkbox shows up on the User's form because the Admin is signed in on the other tab. It works fine if two different browsers are being used.
Is there a way to avoid this?
You may need to find or create a variable that can be used to determine if you're in the admin part of the site. A cheesy way would be to put a before filter in your admin controller(s) that sets an instance variable (#admin_site = true for example) then update your partial thus:
- if admin_signed_in? && #admin_site
.pull-right
= label_tag :internal, "Private"
= f.check_box :internal
However it's considered bad practice by some (Sandi Metz for example) to proliferate the instance variables sent to the view. Also, it's a good idea to pass parameters in to partials explicitly as locals rather than relying on instance variables. (This helps readability and makes it easier to share them in general).
The facade pattern can help here.
http://robots.thoughtbot.com/sandi-metz-rules-for-developers
Edit:
Since using the facade I tend to spurn helpers in general, but you could probably do something like this:
#app/helpers/application_helper.rb
module ApplicationHelper
def show_internal?
request[:controller].in? ['admin']
end
...
Assuming your admin actions are all in a controller called 'admin_controller'.
You can add more admin controllers if you have many, by adding to the array:
request[:controller].in? ['user_admin', 'product_admin']

Destroying an entry using cucumber

I have a Ruby on Rails application in which I'm writing a CRUD test suite for all of my pages.
The first three CRUD actions - Create, Retrieve, and Update I can do no problem.
What I'm having trouble with is the last one - Destroy - since I need to start from the index page of whatever model I'm testing, and all of the links have the same "Destroy" text. If I just follow "Destroy" I get an ambiguous match.
I realize this is probably pretty simple - but I'm new to cucumber and capybara.
Thanks!
If you are listing the elements in a table then you can select a specific element with specific text (for example, if you are testing deletion for users then you can select a table row which has the user fullname as the text.
Something like this:
When /^I click the 'Delete' button for the "([^"]*) user $/ do |value|
within(".users") do
page.find("tr", :text => value).click_link "Delete"
end
end

Capybara skips a controller action but still ends up with the proper view, so the tests pass

This is the most confusing thing I have dealt with in a long time. I have a controller action that sends an e-mail when a comment is made:
class CommentsController < ActionController::Base
def create
#comment = Comment.new(params[:comment])
#comment.author = current_user
#comment.save
CommentMailer.recent_comment_made(#comment).deliver
respond_with(#comment)
end
end
I would like to test it. My tests go to the proper page (I've verified this), where there is a form with action /comments. It then submits the form and returns the view with a successful flash (using FlashResponder) and everything seems great... but the e-mail is never sent. In fact, the entire create action is never called. The weird thing is the same process works in development but sends the e-mail!
Now I know it's getting to the correct controller, because I can add this:
before_filter { raise }
And the tests fail. I can add:
before_filter { p params }
And I see the parameters, which have controller as 'comments' and action as 'create'. But if I add:
def create
raise
...
No exception is raised. In fact I can just comment out the entire create method and the test still passes, with the comment being created and everything. I am not using InheritedResources or anything of that kind. And like I said... it works on development!
I've used save_and_open_page after every step and it all looks good. The form action is correct. The flash message is correct. The assertion that the comment is created is correct... even when the create method is commented out completely.
Originally I thought that it was the wrong controller or that Cucumber was using some older version of my controller for some unknown reason, but when I add the before_filters to raise/print params... that all happens and works as expected.
Does anyone know what could be going on here, or any way I can at least SEE what is going on here? I am all out of ideas. My feature looks like this:
Given I visit the page
And I enter a comment
When I submit the comment
Then the e-mail is delivered
And the comment is saved to the database
These are made more generic than they actually are to conceal the actual intent of the project. The step definition pseudo code is:
visit ...
fill_in ...
find('submit button').click
assert ActionMailer::Base.deliveries.include? ...
assert comments.present?
Pretty simple stuff. Visit a page, submit a form, assert that the stuff in the create action worked.
WOW...
What happened was there was a copy of the controller in our api/v1 directory (but not namespaced to api/v1) on accident. The tests were loading that version, while development was loading the 'actual' version.

How to test link_to_unless_current in RSpec view example group

I'm learning RSpec 2 with Rails 3 and while it's been going along quite nicely so far, I'm having a problem testing the helper link_to_unless_current in a view. What I've been trying to do is use a simple assert_select from a view spec to determine if a link is being generated in the following partial view (HAML):
%article.post{ :id => "post-#{post.id}" }
%header.post-title
%h2= link_to_unless_current post.title, post
.post-content= raw post.body
However, I don't know how to get the view spec to recognize what "current" means because it's a view spec and it only tests the view, not the request. I know this would be a lot simpler in a controller spec, but I think that I should be testing what a view does in its spec and that moving this test out to a controller spec would be confusing things a lot. What I'm asking is: is there any way to tell the view spec, perhaps in a "before" block, what the current page is? Also, am I doing the right thing in respect to organizing my tests? Should this test rightfully reside in a controller spec?
Never mind, I eventually figured it out. You have to stub UrlHelper.current_page? and have it return true if the url options passed match the page you want to act as the current page:
view.stub("current_page?".to_sym) {|options| url_for(options) == post_path(#post) }
I still don't know if this is the way I should be doing RSpec tests, but, whatever, this works. :P

Resources