Capybara checkbox not triggering JS? - capybara

I am trying to test the following form which allows a user to hide their profile by checking a checkbox.
<%= form_tag toggle_hidden_path, id: 'toggle-form', method: :put, remote: true do %>
<%= check_box_tag(:hide, value = '1', checked = #account_is_hidden) %>
<% end %>
When checked or unchecked (changed), the form is submitted using JS.
<script type="text/javascript">
$('#hide').on('change', function() { $(this.form).submit(); });
</script>
My controller action and method within the model toggle the :hidden boolean.
# accountsController
def toggle_hidden
#account = current_user.account
#account.toggle_hidden!
end
# account.rb
def toggle_hidden!
toggle!(:hidden)
end
I am using Rspec and capybara. My test is below
require 'rails_helper'
feature 'Hide and show profile' do
let(:account) {create :account}
scenario 'successfully', js: true do
login_as(account.user)
visit dashboard_path
expect(account.hidden).to eq(false)
expect(page).to have_field('hide')
check('hide')
expect(page).to have_field('hide', checked: true)
expect(account.hidden).to eq(true)
end
end
The test error;
Failures:
1) Hide and show profile successfully
Failure/Error: expect(account.hidden).to eq(true)
expected: true
got: false
(compared using ==)
Any help is greatly appreciated as to why this may not be working..
EDIT
I added a flash notice to display when the checkbox is checked/unchecked.
def toggle_hidden
#account = current_user.account
#account.toggle_hidden!
respond_to do |format|
format.js { flash[:notice] = "Hidden toggled" }
end
end
and updated the spec - but still, test does not pass.
expect(page).to have_content('Hidden toggled')

You have already loaded account so the value of hidden in the object won't change. In order to see the new value you'd need to reload the object
expect(account.reload.hidden).to eq(true)
However, since actions (check/uncheck/click/etc) aren't guaranteed to wait for any behaviors triggered by them it's highly likely the form submission won't have completed (and hence the object not been updated) by the time your expectation runs (hence why expectations on database objects are generally frowned upon in feature specs). If your page has a visible change when the form submit is completed you should set an expectation on that before checking the database object, or better yet just skip testing the database object and instead test what the user can actually see - that their profile is hidden after they've checked the box.

Related

Display data in seed file to a show page

I'm very new to rails. What I'm trying to do is display info i have in a seed file to my views/show page. This is some of my seed file, it's just made up organizations for a school project I'm doing.
`Organization.create(
name: "St. John's church",
location: "222 Bathurst st.",
description: "Church"
)
Organization.create(
name: "Women's Shelter Toronto",
location: "777 Yonge St.",
description: "Womens Shelter"
)
Organization.create(
name: "Toronto Homeless Shelter",
location: "111 King St.",
description: "Shelter"
)`
here is my show page
<h1>Organization#show</h1>
<div class="container">
<h1>Organization:</h1>
<%= #organization.name %>
</div><br>
<div class="container">
<h1>Description:</h1>
</div><br>
and organization controller
def show
#organization = Organization.all
end
def new
#organization = Organization.new
end
def create
#organization = Organization.new(organization_params)
if #organization.save
redirect_to root_url
else
render "new"
end
end
private
def organization_params
params.require(:organization).permit(:name, :description, :location)
end
end
This very basic i know i'm just learning.
First, you should probably use use create! in your seed file so that if any of your creates fail you will see an exception flagged.
Second, what you are using as a show action should more likely be the #index action in your controller as that is the rails convention. When you stray from rails conventions your programming life gets a whole lot heavier and slower.
As an interim manual test you can run between first and second is this: After your seed file has been run, you can do things like:
rails console
Organization.count
Organization.all
to verify that your data is seeded properly.
At that point, you may find implementing and debugging your show, edit and new actions more straightforward.
The purpose of the seeds.rb file is to populate the database after the first time it's created.
After you do that, you can display the newly created data in your database on the page using Active Record.
I see 2 possible problems here:
You don't need the `` at the beginning and end of the seeds.rb file.
You didn't run rake db:seed - This command takes the seeds.rb file and actually creates the records in the database.
In your OrganizationsController#show method you are setting #organization to a list of all organization entries in your database. #organization.name is therefore not a valid method. You can do one of two things. If you only want to show one organization on the show page, which would be typical, you would have to find that organization somehow. When a user requests a show page for a particular organization he typically does so in a RESTful manner, that is through /organizations/:id, where :id is the id of the particular organization he or she would like to show. In your controller try:
def show
#orginazation = Organization.find(params[:id])
end
params[] is how Rails passes information between views and controllers. In this case params[:id] would be 1 if the user requested /organizations/1. Your controller would then go to the database and find the organization whose id was 1.
Now if you want to show a list of all organizations you would do so typically in the #index method. Something like this.
def index
#organizations = Organization.all
end
Then you would have an index.html.erb file in which you would iterate through the #organizations, like so.
<% #organizations.each do |organization| %>
<%= organization.name %>
<%= organization.location %>
<%= organization.description %>
<% end %>
This way when your user requests /organizations, he or she will see a listing of all the organizations in your database.
Edit: Your post also suggests that your controller is called OrganizationController, it should be plural, OrganizationsController and the associated filename should be organizations_controller.rb

How to test a multi-select select box with Capybara in a rails app?

A coworker and I have been struggling to figure out how to test a rails form with a select element, which we sometimes want to be a standard select dropdown and sometimes want to be a multi-select box.
What can we add to our RSpec feature spec to test this? Here's the code we're trying to test:
<% if #use_multi_select %>
<%= select_tag :program, options_for_select(#programs), { multiple: true } %>
<% else %>
<%= select_tag :program, options_for_select(#programs) %>
<% end %>
And here's what I'm thinking for the test:
context 'when #use_multi-select == true' do
it 'displays a multi-select select box' do
expect(page).to have_select('program').with_options(multiple: true)
end
end
context 'when #use_multi-select == false' do
it 'displays a standard select box' do
expect(page).to have_select('program').with_options(multiple: false)
end
end
But that whole with_options thing doesn't work that way. Has anyone done this before and found a workable solution or work-around?
with_options is an option that can be passed to have_select (not a method that is called on have_select) to check that specific option elements are children of the select (others may also exist)
To test whether a select is multiple you can do
expect(page.find(:select, 'program')[:multiple]).to be_truthy
And obviously be_falsey for the opposite
If you're running Capybara 2.6.x you could do
Capybara.modify_selector(:select) do
filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
end
which would then let you do
expect(page).to have_select('program', multiple: true)
The :multiple filter will probably be included in Capybara 2.7 when it's released

Rails - How do I refresh a page without reloading it? (updated)

This question is about problems I ran into after asking my previous question Rails - How do I refresh a page without reloading it?
Have been googling but cannot find a solution.
I'm building a function that gives out a random record every time people go to that page but the random record will change again if the user refresh the page.
Therefore I have tried using cookies
#posts_controller.rb
def new
#random = cookies[:stuff] ||= Stuff.random
end
and call #random.content in my posts/new view since I want only the content of the Stuff model, when I go to the page for the first time is fine but when I refresh the page, it gives me this error
undefined method 'content' for "#<Stuff:0x0000010331ac00>":String
Is there a way to resolve this?
Thanks!
----Update----
I have fixed the content missing problem using <%= show_random(#random)['content'] %> and the Internal Server Error expected Hash (got Array) for param 'post'using
<%= f.hidden_field :stuff_id , :value => show_random(#random)[:id] %>
stuff/new.html.erb
# app/views/stuff/new.html.erb
<%= show_random(#random)[:content] %>
<div>
<%= f.hidden_field :stuff_id , :value => show_random(#random)[:id] %><br>
</div>
But when creating the Post without meeting the validation, it gives me
no implicit conversion of Stuff into String
Extracted source (around line #2):
1
2 <%= show_random(#random)['title'] %>
I think it has something to do with my create action in my Posts_controller.erb
Please have a look below of my Posts_controller.erb
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Post created successfully!"
else
#random = Stuff.where(id: params[:post][:stuff_id]).first
render 'new'
end
end
The first time , as cookies[:stuff] is null, so a new Stuff instance is assigned to both cookies[:stuff] and #random, so the content method call on #random will be fine. But as you store an object into the cookies[:stuff], the value will be converted into a string by rails automatically.
The second time, you visit the page, the cookies[:stuff] is not empty, and is assigned to the #random variable. But as previous saying, the content inside the cookies is a string, so calling content method on a string can not work.
Make your .random method defined on Stuff return a serialized record, which you can then deserialize in your views (possibly with a helper method) to make it a hash:
# app/models/stuff.rb
def self.random
random_record_magic.to_json
end
end
# app/views/stuff/index.html.erb
<%= show_random(#random)['content'] %>
# app/helpers/stuff_helper.rb
def show_random(random)
JSON.parse(random)
end

Capybara choose("radio button") not working

A snapshot of my view:
<%= form_for #request do |f| %>
<div class="form-group">
<%= f.radio_button(:item, "Snow/waterproof shell (upper)") %>
<%= f.label(:item, "Snow/waterproof shell (upper)") %>
</br>
<%= f.radio_button(:item, "Headlamp") %>
<%= f.label(:item, "Headlamp") %>
</div>
Yet on my Rspec integration test file (spec/requests/requests_spec.rb), when I write (note the choose radio button is a part of the form where the user requests an item from a list, and the test is for the resulting page after submission, which should indicate the item that the user requested). I'm using gem 'rspec-rails', '2.13.1'
describe "Requests" do
subject { page }
describe "new request" do
before { visit root_path }
describe "with valid information" do
before do
choose("Snow/waterproof shell (upper)")
click_button submit
end
it { should have_content("Snow/waterproof shell (upper)")
end
end
end
I always get the error:
←[31mFailure/Error:←[0m ←[31mchoose("Snow/waterproof shell (upper)")←[0m
←[31mCapybara::ElementNotFound←[0m:
←[31mUnable to find radio button "Snow/waterproof shell (upper)"←[0m
←[36m # ./spec/requests/requests_spec.rb:24:in `block (4 levels) in <top (required)>'←[0m
Same if I try with choose("Headlamp") or any other option. Any thoughts smart people? This seemed like something that would be so easy...
I've had this issue a number of times. If you choose form elements based on their ID in the dom it's far more reliable:
before do
choose 'request_item_headlamp'
click_button submit
end
I can't tell without looking what ID rails would come up with for the other radio button. Just right click it in chrome, inspect element, and cut and paste the element ID into your test.
I suspect sometimes when choose doesn't reliably check a radio button, it may be because an animation is in progress.
If you suspect this is causing your choose calls to be unreliable, try disabling animations, say by setting their execution time to 0 seconds in your test environment. Alternatively build in a wait period in your test for the animation to finish.

How to bubble up exception in rails

New to rails and running into some issues around best practice error handling.
I have the following code in helper in the /lib folder which is used to apply a coupon to some pricing.
As you can see from code below i'm trying a few different ways to return any errors. Specifically, model.errors.add and flash[:error].
The code below is supposed to return a new price once a valid coupon has been applied. What i'm not sure about is what to return in the case of an error? I can't return false as I am below because this method should really be returning a numeric value, not a boolean. What is the best method to bubble up an error to the view from the lib/helper and stop execution?
def calc_coupon transaction_price, discount_code, current_tenant
coupon = Coupon.where(code: discount_code, tenant_id: current_tenant.id).first
if coupon.nil?
current_tenant.errors.add :base, "No such coupon exists"
return false
else
if coupon.maxed? || coupon.expired?
flash[:error] = "This coupon is no longer valid."
return false
else
transaction_price = coupon.calculate(transaction_price)
end
end
return transaction_price
end
I think you are on the right track but you are struggling because you are mixing view logic with business logic.
Forget the helper class and add the functionality to your model.
By adding the validations to the model rather than a helper class and adding the errors to base your controller action will be able to handle the errors when it tries to save/create the record in the normal way, thus "bubbling up the errors"
e.g.
class MyModel < ActiveRecord ...
validate :validate_coupon
protected
def validate_coupon
# add your validations here - call your boolean check which adds errors to the model
# if validations are ok then calculate your transaction price here
end
end
If you have your checks as public methods on your model then they can be called from anywhere, i.e. from within a view
so a public transaction_price method on your model that first checks for errors and sets the price could be used in a view like so
<%= #some_object.transaction_price %>
As I stated earlier because the validations are added in the model then your controller action just needs to check the value of the call to save and flash the errors as normal if it returns false so no need to do anything custom in your controller actions at all. Just the default scaffolded behaviour.
There are a mountain of validation options available to you. Have a look at
http://guides.rubyonrails.org/active_record_validations_callbacks.html
Hope that makes sense.
UPDATE IN RESPONSE TO COMMENT
1) You have that pretty much spot on. It is merely a suggestion that you could calculate the transaction price as part of the validation. If that doesn't suit your requirement then that's fine. I was just trying to give you the best fit for your question. The thing is that you should have two seperate instance methods then you can call them from wherever you wish which makes it a lot simpler for experimentation and moving stuff around. Go with your suggested solution It's good :)
2) Have a close look at a scaffolded controller and views. You will see how the saves/updates fail (They will fail automatically if you add errors as part of your validation process) using an if statement and you will see how the _form partial displays the errors
This is an example of an form that deals with engines in an app I am working on.
<%= form_for(#engine) do |f| %>
<% if #engine.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#engine.errors.count, "error") %> prohibited this engine from being saved:</h2>
<ul>
<% #engine.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Basically it is checking for errors and looping through them all if any exist to display them.
To handle flash messages you should utilise the applications main layout
The following code will display flash messages inside a div called "info" which you can style however you want
<% unless flash.empty? %>
<div class="info">
<%- flash.each do |name, msg| -%>
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<%- end -%>
</div>
<% end %>
By placing that before the main yield in your application.html.erb in your views/layouts folder you can be sure that whatever page is rendered, flash messages will always be shown to the user
Of course if you are displaying flash messages then maybe you don;t need to display the errors. Sometimes both is the right thing to do and sometimes just one or the other is fine. That's up to you to decide on a case by case basis.
The main thing is that the receiving action for the HTTP POST or HTTP PUT (create or update action) handles the failure to save with or without a flash as you see fit
e.g.
A create action
def create
#engine = Engine.new(params[:engine])
respond_to do |format|
if #engine.save
format.html { redirect_to #engine, notice: 'Engine was successfully created.' }
format.json { render json: #engine, status: :created, location: #engine }
else
flash.now[:notice] = "Failed to save!" # flash.now because we are rendering. flash[:notice] if using a redirect
format.html { render action: "new" }
format.json { render json: #engine.errors, status: :unprocessable_entity }
end
end
end
Hope that clears things up
UPDATE 2
Forgot to mention that flash is just a hash and uses the session cookie so you can set any key you like
e.g.
flash.now[:fred]
This would enable you to style and handle a specific fred notification in a specific view if you wanted a different styling from that supplied by your application layout by adding the code to handle the :fred key (or name of your choice) in a specific view

Resources