Is this failing radio button feature test a Capybara syntax issue? - ruby-on-rails

I have a form with a radio button for a boolean attr prev_cover, displays Yes or No in browser with param values of true or false and saved in db as true or false as data type boolean. I am trying to:
a) model test presence of a selection of the radio buttons
b) feature test the creation of a whole quote record.
With validation of prev_cover in place, I tell capybara to within(".quote_prev_cover") { choose('No') } the create_quote_spec test fails, model quote.rb test passes.
With validation of prev_cover in place, I tell capybara to within(".quote_prev_cover") { choose('Yes') } the create_quote_spec test passes, model quote.rb test passes.
In actual manual browser testing the db record is created when either Yes or No is selected.
When I remove presence validation of prev_cover from quote.rb the create_quote_spec passes with with prev_cover Yes or No selected, but of course the model quote_spec.rb test fails.
I cannot see where the problem is here, is it something to do with the capybara within(".quote_prev_cover") { choose('No') } syntax perhaps? (it finds that css element just fine)
Here's form element of new.html.erb
<div class='container'>
<div class='row'>
<div class='col-md-6'>
<%= simple_form_for #quote do |f| %>
<%= f.input :prev_cover, as: :radio_buttons, label: "Previous cover" %>
<%= f.submit "Get quote", class: 'btn btn-primary' %>
<% end %>
</div>
</div>
</div>
Here's quote.rb validation;
validates :prev_cover, inclusion: { in: [true, false] }
Here's prev_cover element of the called NewQuoteFrom.new being created by Capybara;
within(".quote_prev_cover") { choose('No') }
Here's test that fails if chooses prev_cover 'No' and passes if chooses 'Yes', create_quote_spec.rb
feature 'creating quote request' do
let(:user) { FactoryGirl.create(:user) }
let(:new_quote_form) { NewQuoteForm.new }
before do
login_as(user, :scope => :user)
end
scenario 'completing quote data' do
new_quote_form.visit_page.fill_in_with().submit
expect(page).to have_content('Quote request created')
end
here's the model test that passes whilst model validation is present, quote_spec.rb;
context 'previous cover' do
it 'must be true or false' do
quote = Quote.new(prev_cover: nil)
quote.valid?
expect(quote.errors[:prev_cover]).not_to be_empty
end
end
Failing test message:
1) creating quote request completing quote data
Failure/Error: expect(page).to have_content('Quote request created')
expected to find text "Quote request created" in "Toggle navigation QuoteEngine My Quotes My Account Sign out Complete the below to get a quote * GLA Previous coverYesNois not included in the list * Company name * Company number * Postcode * Industry Financial services Architect Business consultancy Lives overseasYesNo Scheme start date 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 January February March April May June July August September October November December 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Payment frequency Annually Monthly Commission level"
# ./spec/features/create_quote_spec.rb:14:in `block (2 levels) in <top (required)>'
Note the YesNois not included in the list in the error message, using any variation of true or false in the Capybara instruction does not work either.
Even specifying:
<%= f.input :prev_cover, as: :radio_buttons, :collection => [['Yes', true],
['No', false]], label: "Previous cover" %>
in the simpl_form_for in the new.html.erb view still results in the failure with the validation giving Previous coverYesNois not included in the list!?
Even within(".quote_prev_cover") { choose('quote_prev_cover_false') } still fails with the same Previous coverYesNois not included in the list.
It passes if told to choose Yes within(".quote_prev_cover") { choose('Yes') }, almost asif I hadn't already changed from presence: to inclusion: which I most certainly have done.
Actual HTML from firefox inspector:
<div class="form-group radio_buttons optional quote_prev_cover">
<label class="control-label radio_buttons optional">Previous cover</label>
<input name="quote[prev_cover]" value="" type="hidden">
<span class="radio">
<label for="quote_prev_cover_true">
<input class="radio_buttons optional" value="true" name="quote[prev_cover]" id="quote_prev_cover_true" type="radio">Yes</label>
</span>
<span class="radio">
<label for="quote_prev_cover_false">
<input class="radio_buttons optional" readonly="readonly" value="false" name="quote[prev_cover]" id="quote_prev_cover_false" type="radio">No</label>
</span>
</div>

The only way this report makes sense is if your statement "In actual manual browser testing the db record is created when either Yes or No is selected with corresponding boolean values created just fine." isn't true.
That's because you're using validates_presence_of (through validates presence:{...} shortcut) validator which can't be used with boolean fields if false is a valid response. From the validates_presence_of docs
If you want to validate the presence of a boolean field (where the
real values are true and false), you will want to use
validates_inclusion_of :field_name, in: [true, false].
This is due to the way Object#blank? handles boolean values:
false.blank? # => true.
Therefore, if you change from presence validation to
validates :prev_cover, inclusion: { in: [true, false] }
your tests will probably pass.

The issue causing the failure of this Capybara test trying to create a quote record was that it was unable to select the No/false value of a boolean radio button, which due to a simple_form bug had readonly="readonly" set. There are two solutions, courtesy of this post here
1) Within config/initializers/simple_form_bootstrap.rb comment out b.optional :readonly within the :vertical_radio_and_checkboxes wrapper.
2) set your simple form arguments as below:
<%= f.input :prev_cover, as: :radio_buttons, collection: [['Yes', true],
['No', false]], readonly: nil %>
Though option 2 still has Capybara giving you a warning when you run your tests of; Attempt to set readonly element with value: true * This will raise an exception in a future version of Capybara
Thanks to #ThomasWalpole helping me along the way and to platformatec maintainers.

Related

nested_form_fields gem, options_for_select with jQuery chaining not working rails

I am using the nested_form_fields gem - with chained options_for_select. The 'selected option' isn't saving. The chaining, itself, is working. The gem is written in javascript, so there may be a basic conflict between the gem and jQuery chaining. Hopefully, though, I'm overlooking something else.
Model - user.rb:
has_many :state_registrations, inverse_of: :user,
dependent: :destroy
accepts_nested_attributes_for :state_registrations
Model - state_registrations.rb:
belongs_to :user, inverse_of: :state_registrations
View - users/_form.html.erb:
<div class="col-sm-12 col-md-12 form-group">
<%= f.nested_fields_for :state_registrations, :html => { :multipart => true },
wrapper_tag: :div do |state_registration_fields| %>
<div class="row" >
<div class="col-sm-1 col-md-1"></div>
<div class="col-sm-3 col-md-3">
<%= state_registration_fields.label :state_license, "State" %>
<%= state_registration_fields.select :state_license,
options_for_select(#state_licenses_1), {}, {class:
"state_license form-control btn-default btn-block",
name: "state_license"} %>
</div>
....
<div class="col-sm-3 col-md-3">
<%= state_registration_fields.label :state_license_number,
"License Number or 'Exempt' " %>
<%= state_registration_fields.text_field :state_license_number,
class: "form-control input", type: "text", placeholder: "License
Number" %>
</div>
....
Javascript - custon.js:
This follows the excellent work of Mika Tuupola Chained selects plugin for jQuery and Zepto, remote verson.
$(document).ready(function() {
$(".state_license_name_user").remoteChained({
parents : ".state_license",
url : "state_registrations/state_license_name_user",
loading : "Loading...",
clear : true
});
});
Controller - state_registrations_controller.rb:
This is what's chained:
def state_license
#state_licenses = ['State (select)'] + StateRegistration.all.order(:state_license).pluck(:state_license).uniq
end
def state_license_name_user
state_license_name_users = StateRegistration
.where("state_license = ?", params[:state_license])
.order(:state_license_name_user)
.pluck(:state_license_name_user).uniq
render :json => state_license_name_users.map {|value| [value, value]}
end
Controller - users_controller.rb:
def update
#user = User.includes(:state_registrations).find(params[:id])
raise params.inspect
The params returned show that the :state_license_number - which isn't chained - is part of the state_registrations hash. The chained options_for_select, :state_license and :state_license_name_user (not shown in above view), are separate (at the end):
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"xxxx==",
"user"=>{"email"=>"xxx", ..., "state_registrations_attributes"=>{"0"=
{"state_license_number"=>"OREGON-12345", "submitted_by"=>"user"}}, ...,
"state_license"=>"Oregon", "state_license_name_user"=>"Tax Preparer",
"commit"=>"UPDATE Profile", "controller"=>"users", "action"=>"update",
"id"=>"1"}
The three fields, :state_license, :state_license_name_user, and :state_license_number, are part of the state_registrations table. They aren't separately included in the users table. The state_registration and user models are indexed, i.e., the state_registrations table has a user_id field and the users table has a state_registration_id field.
I tried several work-arounds with no success at saving :state_license and :state_license_name_user including using collection_select, using the child index of each record to namespace (although, candidly, I'm not sure I was doing it correctly). I think I need to get this saving before worrying about the chaining. (1) Chaining works when there's only one record. (2) It breaks with multiple records.
The nested_form_fields gem does provide for 'Namespaced Associations'; however, I don't understand the suggested usage [which, nevertheless, I tried, following the example shown - again without success] and / or if its applicable here. Nested_Form_Fields documentation
Because the gem is written in javascript, using options_for_select with jQuery chaining may not be feasible without a re-write of the gem's code. However, I'd like to get some feedback. Thanks in advance.
The extra gems and plugins obscure the real issue, which is in the schema design. state_registrations is acting as both a master table (which would define the available options in the select) and had a user_id field - the design would not work for more than one user with a given license type. It really needs a join table between registrations and users to express the many-many relationship.
Rails supports simple many-many relations with has_and_belongs_to_many, although I usually prefer an explicit join table with has_many :through, because it allows attaching additional information. Example: date the license was obtained, or next expiration.
Choosing Between has_many :through and has_and_belongs_to_many
Disclaimer: I met the user IRL and spent some time discussing the schema and intent to understand what was going on.

Testing datepicker gem with Rspec

I am using the bootstrap-datepicker-rails gem for my app and have created a form where users can pick a start date and an end date.
When the user edits the form I want them to be able to see the dates that they had previously chosen. (e.g. Start: October 31, 2015, End: November 15, 2015).
I am trying to write a test to make sure my app does this, but I'm stuck on the syntax.
user_updates_project_spec.rb
require "rails_helper"
feature "User updates project" do
before (:each) do
#user = create(:user)
login_as(#user, scope: :user)
#project = create(:project, creator: #user,
name: "Building a robot",
start_at: "2015-10-31",
end_at: "2015-11-15", )
end
scenario "and sees correct dates on form" do
visit edit_user_project_path(#user, #project)
expect(".datepicker").to have_content("October 31, 2015")
end
end
The error I get is:
Failure/Error: expect(".datepicker").to have_content("October 31, 2015")
expected to find text "October 31, 2015" in ".datepicker"
This is what my form looks like:
<div class="col-sm-6 pl0">
<%= f.input :start_at, label: "Start Date", as: :string,
input_html: {value:#project.set_start_date_for_form.to_s(:long),
class: "datepicker start-date"} %>
</div>
<div class="col-sm-6 pr0">
<%= f.input :end_at, label: "End Date", as: :string,
input_html: {value: #project.set_end_date_for_form.to_s(:long),
class: "datepicker end-date"} %>
</div>
Any ideas on why it's not working?
The way you've written your expects, what you're really saying is expect the string ".datepicker" to have the content "October 31, 2015" which is never going to be true. expect needs to take an element that the have_content matcher can be run against rather than a string. You could rewrite it like this
expect(page.find(:css, '.datepicker.start-date')).to have_content("October 31, 2015")
although a better method would probably be to use
expect(page).to have_css('.datepicker', text: 'October 31, 2015)
since that will match if the content is changing or being loaded via ajax.
One other thing to note is that since you're using a datepicker widget you might actually need to look at the html that is produced by the JS in a real browser, instead of the html in your view tempalte, to determine exactly which element you need to be checking for the content inside.
I guess there may be a confusing for .start-date and .end-date also you can use .text attribute to check them:
expect(page.find(:css, '.datepicker.start-date').text).to eq('October 31, 2015')
expect(page.find(:css, '.datepicker.end-date').text).to eq('November 15, 2015')
If it fails, you can print the value to debug it:
puts page.find(:css, '.datepicker.start-date').text
It turns out I wasn't checking inside the correct element when testing.
What solved it was:
expect(page).to have_css("#project_start_at[value='October 31, 2015']")
expect(page).to have_css("#project_end_at[value='November 15, 2015']")
The element id's that datepicker set were project_start_at and project_end_at.

Test with capybara and simple form won't check the checkbox

I'm having quite a bit of trouble checking a terms of service box from simple_form in my capybara/rspec test.
Here is my validation:
validates :terms_of_service, acceptance: true, allow_nil: false
If I remove allow_nil: false then the specs all pass even if the box isn't checked. If I leave it, the validation causes the specs to fail.
Here is the code creating the form/checkbox:
= f.label :terms_of_service, "I agree to the #{link_to 'Terms of Service', terms_of_service_path, :target => "_blank"}".html_safe
= f.check_box :terms_of_service
The resulting html:
<label for="candidate_terms_of_service">I agree to the Terms of Service</label>
<input name="candidate[terms_of_service]" type="hidden" value="0">
<input id="candidate_terms_of_service" name="candidate[terms_of_service]" type="checkbox" value="1">
My attempts in my test which I've tried individually:
page.find_by_id("candidate_terms_of_service").check
find(:xpath, "//*[#id='candidate_terms_of_service']").set(true)
find(:css, "#candidate_terms_of_service").set(true)
check 'candidate[terms_of_service]'
check 'I agree to the Terms of Service'
find('#candidate_terms_of_service').check
And resulting failure:
Failure/Error: let(:candidate) { create(:candidate) }
ActiveRecord::RecordInvalid:
Validation failed: Terms of service must be accepted
How do I check this box?
This particular simple_form field gave me a lot of grief, and while I found several different ways mentioned to query and set it, none of them worked (some mentioned in this issue, others from Capybara issues).
I found the following to actually work with a simple_form boolean with RSpec:
find(:xpath, "//label[#for='candidate_terms_of_service']").click
For the sake of completeness considering the different webdrivers and total solutions given, here are the other solutions found but resulted in invalid selector errors. The input selector would actually error about the inability to check simple_form's default hidden input; this error makes sense, the label is the visible and top-most element.
find('#active_form_terms_of_service').set(true)
# or select by label
find('label[for=active_form[terms_of_service]').click
# and select by label and value if multiple boxes
# with the same ID (not sure why this would happen)
find("label[value='1']").click
# or select the input with xpath
find(:xpath, "//input[value='1']").set(true)
Try:
find('#candidate_terms_of_service').check

populating a custom time field on ruby on rails with simple_form

I'm a beginner to ruby and ruby on rails, and I'm attempting to create a complex form using simple_form. Everything is working already, but I wanted to customize the "time" field in a very specific way.
When I use:
<%= f.input :hour %>
It renders two select fields, being the "hour" field populated with options from 00 to 23, and the "minute" field populated with options from 00 to 59.
But that's not what I want. I want to replace the use of two select fields for the time with a single select field with custom pre-populated options, with time ranging from 8am until 22pm, in 15 minutes increments, and also have the text for the options show times with the syntax "8h00", "8h15", "8h30", "8h45", "9h00", "18h00", "18h15", "18h30", "18h45", etc.. being "21h45" the last option available.
I have managed to customize it a bit more using these options:
<%= f.input :hour, :as => :time, :minute_step => 15 %>
Although that solves the 15 minute increment problem, it still renders two select fields.
This would be the html equivalent of what I wanted to have simple_form render for me:
<select name="hour">
<option value="08:00:00">08h00</option>
<option value="08:15:00">08h15</option>
<option value="08:30:00">08h30</option>
<option value="08:45:00">08h45</option>
...
<option value="21:30:00">21h30</option>
<option value="21:45:00">21h45</option>
</select>
I'm pretty sure this is very easy to implement using a collection that loops from 8..21 and then loops again with '00', '15', '30' and '45', and then outputs options in the syntax that I want (hour + "h" + minute).
I want to create a custom "date" field, because there will many date fields in a single form (for a "appointment" related application that I'm creating), but since I'm a beginner on ruby on rails, I'm really lost on what would be the most smart way to implement this. I'm not sure if I should implement a custom field in simple_form, if I should use a helper function, or what.
With help from Carlos Antonio da Silva, from simpleform's mailing list, we've fixed this by creating a custom input like this:
app/inputs/hour_input.rb
class HourInput < SimpleForm::Inputs::Base
def input
#builder.select(attribute_name, hour_options, { :selected => selected_value }, { :class => "input-medium" })
end
private
# The "Selecione..." string could also be translated with something like: I18n.t("helpers.select.prompt')
def hour_options
hour = [['Selecione...', '00:00:00']]
(8..21).each do |h|
%w(00 15 30 45).each do |m|
hour.push ["#{h}h#{m}", "#{"%02d" % h}:#{m}:00"]
end
end
hour
end
def selected_value
value = object.send(attribute_name)
value && value.strftime("%H:%M:%S")
end
end
and then <%= f.input :hora, :as => :hour %> in the view.
With suggestions from simpleform's google mailing list, this is how I fixed my own problem:
in the view file, I created a input like this:
<%= f.input :hour, collection: options_for_time_select, selected: f.object.hour.strftime("%H:%M:%S") %>
And in application_helper.rb file I created a function this way:
module ApplicationHelper
def options_for_time_select
hour = Array.new
for $h in 8..21 do
for $m in ['00', '15', '30', '45'] do
hour.push [$h.to_s + "h" + $m.to_s, "%02d" % $h + ":" + $m + ":00"]
end
end
hour
end
end
I have no idea if this is the most elegant way to solve this problem, or if it really works on every scenario. I would love to get corrections or a better solution, if possible.

globalize3 and easy_globalize3_accessors validation

I'm using gems: globalize3 and easy_globalize3_accessors.
I have a problem with validations. For example, I have Post model:
class Post
translates :title, :content
globalize_accessors :locales => [:en, :ru], :attributes => [:title, :content]
validates :title, :content, :presence => true
end
and form:
= form_for #post do |f|
-I18n.available_locales.each do |locale|
= f.text_field "title_#{locale}"
= f.text_area "content_#{locale}"
it looks like in view (if I18n.locale = :ru):
<form action="/ru/posts" method="post">
<input id="post_title_ru" name="post[title_ru]" type="text" />
<textarea cols="40" id="post_content_ru" name="vision[content_ru]"></textarea>
<input id="post_title_en" name="post[title_en]" type="text" />
<textarea cols="40" id="post_content_en" name="vision[content_en]"></textarea>
<input name="commit" type="submit" value="Создать Видение" />
</form>
If I fill in the fields only in Russian, the validation passes, if I wanted to post was in English only, and fill only the English field (when I18n.locale = :ru), the validation fails
Title can't be blank
Content can't be blank
As I understand it, there is a problem in the attributes, validation checks only the first attributes :title_ru and :content_ru. And to the rest of attributes (:content_en and :title_en) check does not reach.
how to make a second data validator to check if the validation of the first group of attributes is not passed?
thanks in advance
validate :titles_validation
def titles_validation
errors.add(:base, "your message") if [title_ru, title_en].all? { |value| value.blank? }
end
The problem is that globalize3 is validating the title for whatever locale you are currently in. If you want to validate for every locale (and not just the current locale), you have to explicitly add validators for the attribute in each locale (as #apneadiving pointed out).
You should be able to generate these validators automatically by cycling through I18n.available_locales:
class Post < ActiveRecord::Base
I18n.available_locales.each do |locale|
validates :"title_#{locale}", :presence => true
end
...
end

Resources