Testing datepicker gem with Rspec - ruby-on-rails

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.

Related

Rails date_select undefined method string

I need some help here,
I'm using the date_select helper from Rails in a form to get a date in the format %d/%m/%Y. The helper seems to work fine in development but it doesn't in test mode.
In test mode, I got failure/error when a failed POST is made and the test needs to re-render the view. I got the follow failure/error message:
Failure/Error: <%= f.date_select :birthdate,
ActionView::Template::Error:
undefined method `year' for "{3=>15, 2=>6, 1=>2001}":String
or, depending on how I format the date
ActionView::Template::Error:
undefined method `year' for "15/06/2001":String
It only happens with failed posts (when is needed to re-render the form), both for rspec request and feature tests. In development mode, it works fine to re-render de form.
My tests
# feature test
it "sign up with false credential" do
users = FactoryBot.attributes_for(:users)
visit 'users/sign_up'
fill_in 'user_name', :with => "test"
find_by_id('user_birthdate_3i').find("option[value='15']").select_option
find_by_id('user_birthdate_2i').find("option[value='6']").select_option
find_by_id('user_birthdate_1i').find("option[value='2001']").select_option
click_button('Save')
expect(page).to have_selector "#error_explanation"
end
And
# request test
it "signs usuario_candidato up with wrong attributes" do
users = FactoryBot.attributes_for(:users)
expect{ post user_registration_path,
:params => {
"user" => {
"name" => user[:name],
"birthdate" => "2018/12/31"
}
}
}.to change{ User.count }.by(0)
end
The form
<%= f.date_select :birthdate,
options = {
start_year: Date.current.year,
end_year: 1950,
order: [:day, :month, :year],
prompt: { day: 'day', month: 'month', year: 'year' }
}
%>
Some observations:
The same test format works for feature and request when the is a
successful post.
I'm using locale PT-BR, with default format to %d/%m/Y%
I'm using Postgres, with birthdate as Date type.
I'm using Devise controller to this form, and I didn't override the controller's actions.
As I already said, development mode don't reproduce this error.
I already try different date format (e.g %m/%d%/%Y or year/month/day) before posting, it always return the same error. Because of that, my suggestion to the problem is: after the post fails, the return is a String when it should be a Date class.
Anyone already face this problem?
Any additional information, just ask on comments.
Thanks in advance!
UPDATE
After 6 days of posting, I was trying a suggestion someone gave on comments, and it happens that the tests passed, no error was raised. I didn't make the suggestion, I just change from a text_field to the date_field (as I wasn't able to use date_field, I changed to text_field and moved on to the next problem).
I found a certain inconsistency at how Rails works with date. Before the first raise of the problem, it was passing the tests normally. Then the error happened, now it's not happen anymore.
I have similar problem with a Regex that I use in my model for validate dates:
Model
# dd/mm/yyyy
VALID_DATE = /\A[0-9]{2}\/[0-9]{2}\/[0-9]{4}\z/
validates :birthdate, presence: true, format: { with: VALID_DATE, message: "must be in the format DD/MM/YYYY" }
When I create the validation, it works fine. Values in the format yyyy-mm-dd used not pass in model tests. In the next day, Rspec started to raise errors saying that yyyy-mm-dd and others should be valid (therefore, ignoring the regex validation)
Model test error
User birthdate should be invalid
Failure/Error: expect(user).to_not be_valid
expected #<User id: nil, name: "Sra. Margarida Sophie Santana", birthdate: "2000-12-29", created_at: nil, updated_at: nil> not to be valid
The only thing I have extra for date is a date_formats.rb in config/initializer with the follow value:
Date::DATE_FORMATS[:default] = "%d/%m/%Y"
I have it for a long time.
Apparently Rails changes when it decides to transform string values in date values (when it was passing the regex validation test, it was passing the date as string, when the problem raise up the date was been transformed from string to date before the validation).

Rails reverses day and month when saving DateTimePicker form field

I am trying to use Tempus Dominus Bootstrap 4 in a form on a Ruby on Rails 5.2 app so the user can select a date. Here is the code:
app/views/events/_form.html.erb
<div class='form-group'>
<%= f.label :starts_at, 'Start Date and Time', class: 'control-label' %>
<div class="input-group date" id="datetimepicker1" data-target-input="nearest">
<%= f.text_field(:starts_at, class: "form-control datetimepicker-input", data: {target:"#datetimepicker1"}) %>
<div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fas fa-calendar-plus"></i></div>
</div>
</div>
</div>
app/assets/javascripts/events.js
$(function () {
$('#datetimepicker1').datetimepicker();
});
app/controllers/events_controller.rb
def create
#event = Event.new(event_params)
#event.user = current_user
if #event.save
redirect_to #event, notice: 'Event was successfully created.'
else
render :new
end
end
I want to submit the format with month first. When I pick a date June 9, 2018 and submit the form, it saves it to the database with the month and day reversed: Sept 6, 2018. When I look at the params after the form is submitted, the format is 06/09/2018 6:00 PM with month first, but Ruby/Rails converts it to a datetime object assuming day first.
How do I tell Ruby that the month is first when converting it to a date object? Can I do something in the controller before saving it?
You can tell Ruby how to read time string:
DateTime.strptime('06/09/2018', '%m/%d/%Y')
More details you can see in this answer.
Edited:
Also, you can set the default date format as there:
Add this to config/initializers/date_time.rb
Date::DATE_FORMATS[:default] = "%m/%d/%Y"
I found out you can change the date format to one Ruby correctly understands and that is easily readable by users. Change the JavaScript to:
$(document).on('turbolinks:load', function() {
$('#datetimepicker1').datetimepicker(
'format', 'MMMM D, YYYY h:mm A'
);
});
This will display the date to the user as June 9, 2018 6:30 PM. Date format comes from Moment.js http://momentjs.com/docs/#/displaying/format/ but is added to the format option of Tempus Dominus.
As a side note, to get tempus dominus boostrap 4 working in Rails, you have to also load moment.js. Either call the cdn or add the momentjs-rails gem. If the latter then in your application.js file you need to require it, something like this:
# app/assets/javascripts/application.js
...
//= require moment
//= require tempusdominus-bootstrap-4.js

Is this failing radio button feature test a Capybara syntax issue?

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.

Error when submitting a form with two different models

I have an office model which is a list of all the current offices. I also have a calendar model which will just act as a company calendar. I am trying to get a dropdown of all the current offices to display on the localhost:3000/calendars/new so people can see where the event will be taking place. When I go to submit the form, I get the error shown below. I have posted all relevant code as well. Thanks in advance.
Calendar.rb:
class Calendar < ActiveRecord::Base
belongs_to :office
end
Office.rb:
class Office < ActiveRecord::Base
has_many :calendars
end
calendars_controller:
def new
#calendar = Calendar.new
#offices = Office.all
end
_form.html.erb:
<div class="field">
<%= f.label :office_id, class: "general-text-label" %><br>
<%= collection_select :calendar, :office, #offices, :id, :name, {include_blank: true}, {class: "selectize"} %>
</div>
Error:
Parameters:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"lNP3u+Hs2FYsTBTClWuwJWiwW8HTFECzGVD4CdEOgOF5WD2eNiMNHtQuHjHpynJp7CaIDio09/mhvQg5rLhgtA==", "calendar"=>{"name"=>"Listing Agent Workshop", "description"=>"ffhfh", "date"=>"Friday Feb 17, 2017", "time"=>"4:00 PM", "office"=>"2"}, "commit"=>"Save"}
Rails is trying to infer which Office to associate with your new Calendar. Your calendar is being built as:
Calendar.new({"name"=>"Listing Agent Workshop", "description"=>"ffhfh", "date"=>"Friday Feb 17, 2017", "time"=>"4:00 PM", "office"=>"2"})
Rails knows the office key is an associated model but it expect the value to be an actual instance of an Office, instead here it's just a string.
Instead, you should either specify the id and let rails look it up or find the object first if that is a concern.
First way (change the params):
Calendar.new({"name"=>"Listing Agent Workshop", "description"=>"ffhfh", "date"=>"Friday Feb 17, 2017", "time"=>"4:00 PM", "office_id"=>"2"})
Better way:
office = Office.find(calendar_params[:office])
calendar_params[:office] = office
Calendar.new(calendar_params)

How to select date from a select box using Capybara in Rails 3?

I'm writing a spec for a controller in Rails 3 project using RSpec and Capybara, and I want to select current date from a select box. I tried:
select Date.today, :from => 'Date of birth'
but the spec fails and I get error:
Failure/Error: select Date.today, :from => 'Date of birth'
NoMethodError:
undefined method `to_xpath' for Mon, 18 Jul 2011:Date
How to fix it?
P.S. In view file I use simple_form_for tag and the select box is generated by code:
f.input :date_of_birth
Had the same problem. I googled at lot and solved it this way:
Wrote date select macros into /spec/request_macros.rb
The select_by_id method is necessary for me, because the month is dependent on the translation
module RequestMacros
def select_by_id(id, options = {})
field = options[:from]
option_xpath = "//*[#id='#{field}']/option[#{id}]"
option_text = find(:xpath, option_xpath).text
select option_text, :from => field
end
def select_date(date, options = {})
field = options[:from]
select date.year.to_s, :from => "#{field}_1i"
select_by_id date.month, :from => "#{field}_2i"
select date.day.to_s, :from => "#{field}_3i"
end
end
Added them to my /spec/spec_helper.rb
config.include RequestMacros, :type => :request
Now in my integration tests in spec/requests i can use
select_date attr[:birthday], :from => "user_birthday"
Thanks to http://jasonneylon.wordpress.com/2011/02/16/selecting-from-a-dropdown-generically-with-capybara/ and https://gist.github.com/558786 :)
You need to specify the exact value as it's in the select menu in html. So if your select has values like "2011/01/01" then you need to write:
select '2011/01/01', :from => 'Date of birth'
Your code fails because you pass a date object.
with credit to Markus Hartmair for an excellent solution, I prefer to use labels as selectors because of improved readability. So my version of his helper module is:
module SelectDateHelper
def select_date(date, options = {})
field = options[:from]
base_id = find(:xpath, ".//label[contains(.,'#{field}')]")[:for]
year, month, day = date.split(',')
select year, :from => "#{base_id}_1i"
select month, :from => "#{base_id}_2i"
select day, :from => "#{base_id}_3i"
end
end
call it like this:
select_date "2012,Jan,1", :from => "From date"
I found a clean solution for rspec and capybara to test using date and time select methods, where in your HTML you use a datetime select or date select. This works with Rails 4, RSpec 3.1 and Capybara 2.4.4.
Say in your HTML form you have the following:
<%= f.datetime_select(:start_date, {default: DateTime.now, prompt: {day: 'Choose day', month: "Choose month", year: "Choose year"}}, {class: "date-select"}) %>
the DateTime Select View helper will create 5 select fields with ids such as id="modelname_start_date_1i", where the id the is appended with 1i, 2i, 3i, 4i, 5i. By default Year, Month, Day, Hour, Minute. If you change the order of the fields, make sure to change the feature helper below.
1) Create a Feature Helper for dates and times helpers
spec/support/helpers/date_time_select_helpers.rb
module Features
module DateTimeSelectHelpers
def select_date_and_time(date, options = {})
field = options[:from]
select date.strftime('%Y'), :from => "#{field}_1i" #year
select date.strftime('%B'), :from => "#{field}_2i" #month
select date.strftime('%-d'), :from => "#{field}_3i" #day
select date.strftime('%H'), :from => "#{field}_4i" #hour
select date.strftime('%M'), :from => "#{field}_5i" #minute
end
def select_date(date, options = {})
field = options[:from]
select date.strftime('%Y'), :from => "#{field}_1i" #year
select date.strftime('%B'), :from => "#{field}_2i" #month
select date.strftime('%-d'), :from => "#{field}_3i" #day
end
end
end
Note that for the day I use %-d that gives you a non-padded numeric value (i.e. 4) instead of %d that has a zero-padded numeric value (i.e. 04). Check the date formats with strftime
2) You then need to include your date and time helpers methods in spec/support/helpers.rb so you can use them in any spec file.
require 'support/helpers/date_time_select_helpers'
RSpec.configure do |config|
config.include Features::DateTimeSelectHelpers, type: :feature
end
3) In your Spec file you can call your helper. For example:
feature 'New Post' do
scenario 'Add a post' do
visit new_post_path
fill_in "post[name]", with: "My post"
select_date_and_time(2.days.from_now, from:"post_start_date")
click_button "Submit"
expect(page).to have_content "Your post was successfully saved"
end
end
A slight adaption of Markus's answer:
def select_date(date, options = {})
raise ArgumentError, 'from is a required option' if options[:from].blank?
field = options[:from].to_s
select date.year.to_s, :from => "#{field}_1i"
select Date::MONTHNAMES[date.month], :from => "#{field}_2i"
select date.day.to_s, :from => "#{field}_3i"
end
Thanks to Dylan for pointing it out, but in case anyone is looking for the cucumber version, you can use this:
select_date("Date of birth", :with => "1/1/2011")
For more information, see select_date.
Given the following Formtastic code renders Rails default date selector:
= f.input :born_on, end_year: Time.now.year, start_year: 60.years.ago.year
In your spec, break the date into separate calls to each individual select tag:
select '1956', from: 'person_born_on_1i'
select 'July', from: 'person_born_on_2i'
select '9', from: 'person_born_on_3i'
I don't like that this code is so aware of the HTML, but it does work with the versions of gems at this time.
Gems:
Capybara 2.1.0
Formtastic 2.2.1
Rails 3.2.13
RSpec 2.13.0
In my particular situation, I'm adding potentially multiple date select fields to the page with accepts_nested_attributes_for functionality. This means, I'm not sure what the full id or name of the fields are going to be.
Here's the solution I came up with in case it helps anyone else Googling this:
I'm wrapping the date select field in a container div with a class:
<div class='date-of-birth-container'>
<%= f.date_select :date_of_birth %>
</div>
Then in my feature spec:
within '.date-of-birth-container' do
find("option[value='1']", text: 'January').select_option
find("option[value='1']", text: '1').select_option
find("option[value='1955']").select_option
end
Here's a helper method I wrote for it:
def select_date_within_css_selector(date, css_selector)
month_name = Date::MONTHNAMES.fetch(date.month)
within css_selector do
find("option[value='#{date.month}']", text: month_name).select_option
find("option[value='#{date.day}']", text: date.day.to_s).select_option
find("option[value='#{date.year}']").select_option
end
end
Then using the helper:
select_date_within_css_selector(Date.new(1955, 1, 1), '.date-of-birth-container')
The following worked for me, using a date_field:
fill_in "Date", with: DateTime.now.strftime('%m/%d/%Y')
For Rails 4, in case somebody gets to this question without being limited to Rails 3.
select '2020', from: 'field_name_{}_1i'
select 'January', from: 'field_name_{}_2i'
select '1', from: 'field_name_{}_3i'
You can of course extract this to a helper and make it dynamic.
It looks like this one has been sufficiently covered, but see Capybara's docs for an official answer. You can select by name, id, or label text.

Resources