Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for alternative select boxes date field helpers #535

Merged
merged 1 commit into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on how to contribute to Cucumber.

### Fixed

*
* Date input support in Rails 7 ([#535](https://github.com/cucumber/cucumber-rails/pull/535) [mgrunberg])

## [v2.5.0](https://github.com/cucumber/cucumber-rails/compare/v2.4.0...v2.5.0) (2022-03-07)

Expand Down
47 changes: 47 additions & 0 deletions features/capybara_javascript_drivers.feature
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,53 @@ Feature: Capybara Javascript Drivers
6 steps (6 passed)
"""

Scenario: Support non HTML5 date inputs
Given I have created a new Rails app and installed cucumber-rails
And I force selenium to run Firefox in headless mode
When I run `bundle exec rails g scaffold appointment name:string when:datetime`
And I force "app/views/appointments/_form.html.erb" to use select boxes for dates
And I write to "features/create_appointment.feature" with:
"""
@javascript
Feature: Create appointments
Scenario: Create an appointment using the Web Interface
Given I am on the new appointment page
When I fill in "Cucumber Trainee" for "Name"
And I select "2026-02-20 15:10:00 UTC" as the "When" date and time
And I press "Create Appointment"
Then I should see "Cucumber Trainee"
And I should see "2026-02-20 15:10:00 UTC"
"""
And I write to "features/create_appointment_steps.rb" with:
"""
Given('I am on the new appointment page') do
visit new_appointment_path
end

When('I fill in {string} for {string}') do |value, field|
fill_in(field, with: value)
end

When('I press {string}') do |button|
click_button(button)
end

When('I select {string} as the {string} date and time') do |datetime, selector|
select_datetime(datetime, from: selector)
end

Then('I should see {string}') do |text|
expect(page).to have_content(text)
end
"""
And I run `bundle exec rake db:migrate`
And I run `bundle exec rake cucumber`
Then the feature run should pass with:
"""
1 scenario (1 passed)
6 steps (6 passed)
"""

Scenario: Use direct DB injection
Given I have created a new Rails app and installed cucumber-rails
And I force selenium to run Firefox in headless mode
Expand Down
6 changes: 6 additions & 0 deletions features/step_definitions/cucumber_rails_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
step 'I append to "features/support/env.rb" with:', selenium_config
end

Given('I force {string} to use select boxes for dates') do |file|
content = File.read(expand_path(file))

overwrite_file(file, content.gsub(/\.(datetime|time|date)_field/, '.\1_select'))
end

When('I run the cukes') do
run_command_and_stop('bundle exec cucumber')
end
Expand Down
38 changes: 26 additions & 12 deletions lib/cucumber/rails/capybara/select_dates_and_times.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ module SelectDatesAndTimes
# Select a Rails date. Options hash must include from: +label+
def select_date(date, options)
date = Date.parse(date)
if ::Rails::VERSION::MAJOR >= 7
# Rails 7 generates date fields using input type="date". Capybara support's them
base_dom_id = get_base_dom_from_options(options)

# Rails 7 use HTML5 input type="date" by default. If input is not present fallback to plain select boxes alternative.
# It's safe to use has_css? without waiting/retry. We already know field's label is visible
if html5_input_field_present?(base_dom_id)
fill_in options[:from], with: date
else
base_dom_id = get_base_dom_id_from_label_tag(options[:from])

find(:xpath, ".//select[@id='#{base_dom_id}_1i']").select(date.year.to_s)
find(:xpath, ".//select[@id='#{base_dom_id}_2i']").select(I18n.l(date, format: '%B'))
find(:xpath, ".//select[@id='#{base_dom_id}_3i']").select(date.day.to_s)
Expand All @@ -23,30 +24,43 @@ def select_date(date, options)
# Select a Rails time. Options hash must include from: +label+
def select_time(time, options)
time = Time.zone.parse(time)
if ::Rails::VERSION::MAJOR >= 7
# Rails 7 generates date fields using input type="time". Capybara support's them
base_dom_id = get_base_dom_from_options(options)

# Rails 7 use HTML5 input type="time" by default. If input is not present fallback to plain select boxes alternative.
# It's safe to use has_css? without waiting/retry. We already know field's label is visible
if html5_input_field_present?(base_dom_id)
fill_in options[:from], with: time
else
base_dom_id = get_base_dom_id_from_label_tag(options[:from])

find(:xpath, ".//select[@id='#{base_dom_id}_4i']").select(time.hour.to_s.rjust(2, '0'))
find(:xpath, ".//select[@id='#{base_dom_id}_5i']").select(time.min.to_s.rjust(2, '0'))
end
end

# Select a Rails datetime. Options hash must include from: +label+
def select_datetime(datetime, options)
if ::Rails::VERSION::MAJOR >= 7
# Rails 7 generates datetime fields using input type="datetime-local". Capybara support's them
base_dom_id = get_base_dom_id_from_label_tag(options[:from])

# Rails 7 use HTML5 input type="datetime-local" by default. If input is not present fallback to plain select boxes alternative.
# It's safe to use has_css? without waiting/retry. We already know field's label is visible
if html5_input_field_present?(base_dom_id)
fill_in options[:from], with: DateTime.parse(datetime)
else
select_date(datetime, options)
select_time(datetime, options)
extended_options = options.merge(base_dom_id: base_dom_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this bit here. Everything else is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an optimization. I'm passing down base_dom_id to select_date and select_time so they don't need to find the label and extract the select/input id again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right I've spotted it now. It's not really extending the options, that's where I got confused. It's added the existing dom id to the options hash and then adding a guard clause to the get_base_dom_from_options method.

Although I'm not sure what I'd call it though. I think this area needs a lot of TLC and fixes but for now this will do. Thanks for explanation

select_date(datetime, extended_options)
select_time(datetime, extended_options)
end
end

private

def html5_input_field_present?(base_dom_id)
::Rails::VERSION::MAJOR >= 7 && page.has_css?("##{base_dom_id}", wait: 0)
end

def get_base_dom_from_options(options)
options[:base_dom_id] || get_base_dom_id_from_label_tag(options[:from])
end

# @example "event_starts_at_"
def get_base_dom_id_from_label_tag(field)
find(:xpath, ".//label[contains(., '#{field}')]")['for'].gsub(/(_[1-5]i)$/, '')
Expand Down