Skip to content

Commit

Permalink
Fallback to select if array of checkboxes fail
Browse files Browse the repository at this point in the history
* Allows fields that are arrays of strings to be passed to `<select multiple>`
inputs
* Update README.md
* Add coverage for Array of checkboxes
* Rescue `Capybara` errors and rethrow as Formulaic
* Check for presence of all options in ArrayInput
* Raise Formulaic::InputNotFound with useful message if not all options
  are present in either a select[multiple]'s options or as checkboxes.
* Ensure checkboxes exist with `:has` http://dev.w3.org/csswg/selectors4/#relational
* Extract `SelectInput` and `CheckboxInput`
 * Both are subclasses of `ArrayInput`. Now, `ArrayInput#fill` chains together
   successive calls to `SelectInput#fill` and `CheckboxInput#fill`, finally
   failing with an informative exception if neither inputs exist on the page.
* Enure that I18n's aren't humanized
  • Loading branch information
seanpdoyle committed Sep 19, 2014
1 parent ed47902 commit 4258e08
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ around `I18n.t`.
| `Date`, `ActiveSupport::TimeWithZone` | `select` year, month, and day |
| `TrueClass` | `check` |
| `FalseClass` | `uncheck` |
| `Array` | `check` each array member, which should all be strings |
| `Array` | `check` or `select` each array member, which should all be strings. If not all items can be selected or checked, an error will be thrown.|
| `File` | `attach_file` with `File#path` |

* Formulaic is currently tied to `simple_form` translations and field structure.
Expand Down
33 changes: 32 additions & 1 deletion lib/formulaic/inputs/array_input.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
module Formulaic
module Inputs
class ArrayInput < Input
def initialize(label, value)
@label = label
@value = value
end

def fill
value.each { |checkbox| check checkbox }
attempt_to_fill_selects ||
attempt_to_fill_checkboxes ||
raise_input_error
end

private

def attempt_to_fill_selects
SelectInput.new(label, value).fill
end

def attempt_to_fill_checkboxes
CheckboxInput.new(label, value).fill
end

def contains_all_options?(nodes)
nodes.map(&:text).to_set.superset?(value.to_set)
end

def raise_input_error
raise(
InputNotFound,
%[Unable to find checkboxes or select[multiple] "#{label}" containing all options #{value.inspect}.]
)
end
end
end
end

require 'formulaic/inputs/checkbox_input'
require 'formulaic/inputs/select_input'
36 changes: 36 additions & 0 deletions lib/formulaic/inputs/checkbox_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Formulaic
module Inputs
class CheckboxInput < ArrayInput
def fill
if has_check_boxes?
check_boxes
true
else
false
end
end

private

def has_check_boxes?
contains_all_options?(checkbox_labels)
end

def check_boxes
value.each { |checkbox| check checkbox }
end

def checkbox_labels
all(checkbox_labels_selector)
end

def checkbox_name_selector
"input[type='checkbox'][name='#{label.model_name}[#{label.attribute}][]']"
end

def checkbox_labels_selector
"#{checkbox_name_selector} ~ label,label:has(#{checkbox_name_selector})"
end
end
end
end
37 changes: 37 additions & 0 deletions lib/formulaic/inputs/select_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Formulaic
module Inputs
class SelectInput < ArrayInput
def fill
if has_multiple_select?
select_options
true
else
false
end
end

private

def select_options
value.each { |option| select option, from: label.to_str }
end

def has_multiple_select?
has_select? && select_is_multiple?
end

def has_select?
has_field?(label, type: "select")
end

def select_is_multiple?
select_element[:multiple].present? &&
contains_all_options?(select_element.all("option"))
end

def select_element
@select_element ||= find_field(label, type: "select")
end
end
end
end
4 changes: 2 additions & 2 deletions lib/formulaic/label.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module Formulaic
class Label
attr_reader :model_name, :attribute, :action

def initialize(model_name, attribute, action)
@model_name = model_name
@attribute = attribute
Expand All @@ -17,8 +19,6 @@ def to_str

private

attr_reader :model_name, :attribute, :action

def translate
I18n.t(lookup_paths.first, scope: :'simple_form.labels', default: lookup_paths).presence
end
Expand Down
52 changes: 52 additions & 0 deletions spec/features/fill_in_user_form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,58 @@
expect(input(:user, :age).value).to eq '10'
end

it 'raises a useful error if selecting multiple from a normal select' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, awesome: ['Yes', 'No'])

expect { form.fill }
.to raise_error(
Formulaic::InputNotFound,
'Unable to find checkboxes or select[multiple] "Are you awesome?" containing all options ["Yes", "No"].'
)
end

it 'raises a useful error if not all select options are present' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, likes: ['Ruby', 'Perl'])

expect { form.fill }
.to raise_error(
Formulaic::InputNotFound,
'Unable to find checkboxes or select[multiple] "Your Likes" containing all options ["Ruby", "Perl"].'
)
end

it 'selects an array of strings' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, likes: ['Ruby', 'Rails'])

form.fill

expect(input(:user, :likes).value).to eq ['ruby', 'rails']
end

it 'raises a useful error if not all checkboxes are present' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, dislikes: ['Java', 'Go'])

expect { form.fill }
.to raise_error(
Formulaic::InputNotFound,
'Unable to find checkboxes or select[multiple] "Your Dislikes" containing all options ["Java", "Go"].'
)
end

it 'selects an array of checkboxes' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, dislikes: ['Java', 'PHP'])

form.fill

expect(page).to have_checked_field "Java"
expect(page).to have_checked_field "PHP"
end

it 'selects a string if there is no input' do
visit 'user_form'
form = Formulaic::Form.new(:user, :new, awesome: 'Yes')
Expand Down
44 changes: 37 additions & 7 deletions spec/fixtures/user_form.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
<form accept-charset="UTF-8" action="/users" class="simple_form new_user" id="new_user" method="post" novalidate="novalidate">
<div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value=""><input name="authenticity_token" type="hidden" value="O6ORla31JvEaWroCuiA7bArM15ztDotMfPAbNW0v61g="></div>
<div class="input string required user_name"><label class="string required" for="user_name"><abbr title="required">*</abbr> Display name</label><input class="string required" id="user_name" maxlength="255" name="user[name]" size="50" type="text"></div>
<div class="input email required user_email"><label class="email required" for="user_email"><abbr title="required">*</abbr> Email</label><input class="string email required" id="user_email" maxlength="255" name="user[email]" size="50" type="email"></div>
<div class="input string required user_name"><label class="string required"
for="user_name"><abbr title="required">*</abbr> Your Display name</label><input class="string required" id="user_name" maxlength="255" name="user[name]" size="50" type="text"></div>
<div class="input email required user_email"><label class="email required"
for="user_email"><abbr title="required">*</abbr> Your Email</label><input class="string email required" id="user_email" maxlength="255" name="user[email]" size="50" type="email"></div>
<div class="input tel required user_phone"><label class="tel required" for="user_phone"><abbr title="required">*</abbr> Phone Number</label><input class="string phone required" id="user_phone" maxlength="255" name="user[phone]" size="50" type="tel"></div>
<div class="input integer required user_age"><label class="integer required" for="user_age"><abbr title="required">*</abbr> Age</label><input class="numeric integer age required" id="user_age" maxlength="255" name="user[age]" size="50" type="number"></div>
<div class="input integer required user_age"><label class="integer required"
for="user_age"><abbr title="required">*</abbr> Your Age</label><input class="numeric integer age required" id="user_age" maxlength="255" name="user[age]" size="50" type="number"></div>
<div class="input url required user_url"><label class="tel required" for="user_url"><abbr title="required">*</abbr> Website</label><input class="string url required" id="user_url" maxlength="255" name="user[url]" size="50" type="url"></div>
<div class="input file optional user_avatar"><label class="file optional" for="user_avatar">Avatar</label><input class="file optional" id="user_avatar" name="user[avatar]" type="file" /></div>
<div class="input password optional user_password"><label class="password optional" for="user_password">Password</label><input class="password optional" id="user_password" name="user[password]" size="50" type="password"></div>
<div class="input file optional user_avatar"><label class="file optional"
for="user_avatar">Your Avatar</label><input class="file optional" id="user_avatar" name="user[avatar]" type="file" /></div>
<div class="input password optional user_password"><label class="password
optional" for="user_password">Your Password</label><input class="password optional" id="user_password" name="user[password]" size="50" type="password"></div>
<div class="input select required user_awesome">
<label class="select required" for="user_awesome"><abbr title="required">*</abbr> Are you awesome?</label>
<select class="select required" id="user_awesome" name="user[awesome]">
Expand All @@ -15,9 +20,34 @@
<option value="false">No</option>
</select>
</div>
<div class="input text required user_bio"><label class="text required" for="user_bio"><abbr title="required">*</abbr> Biography</label><textarea class="text required" cols="40" id="user_bio" name="user[bio]" rows="20" style="overflow: hidden; word-wrap: break-word; resize: horizontal; height: 464px;"></textarea></div>
<div class="input select user_likes">
<label class="select" for="user_likes">Your Likes</label>
<select class="select" id="user_likes" name="user[likes][]" multiple>
<option value=""></option>
<option value="ruby">Ruby</option>
<option value="rails">Rails</option>
<option value="git">Git</option>
</select>
</div>
<div class="input check_boxes optional user_dislikes">
<label class="check_boxes optional">Your Dislikes</label>
<span class="checkbox">
<input class="check_boxes optional" id="user_dislikes_1" name="user[dislikes][]" type="checkbox" value="java">
<label class="collection_check_boxes" for="user_dislikes_1">Java</label>
</span>
<span class="checkbox">
<label class="collection_check_boxes" for="user_dislikes_2">
PHP
<input class="check_boxes optional" id="user_dislikes_2" name="user[dislikes][]" type="checkbox" value="php">
</label>
</span>
<input name="user[dislikes][]" type="hidden" value="">
</div>
<div class="input text required user_bio"><label class="text required"
for="user_bio"><abbr title="required">*</abbr> Your Biography</label><textarea class="text required" cols="40" id="user_bio" name="user[bio]" rows="20" style="overflow: hidden; word-wrap: break-word; resize: horizontal; height: 464px;"></textarea></div>
<div class="input date required user_date_of_birth">
<label class="date required" for="user_date_of_birth_1i"><abbr title="required">*</abbr> Date of birth</label>
<label class="date required" for="user_date_of_birth_1i"><abbr
title="required">*</abbr> Your Date of birth</label>
<select class="date required" id="user_date_of_birth_1i" name="user[date_of_birth(1i)]">
<option value="2001">2001</option>
<option value="2000">2000</option>
Expand Down
14 changes: 8 additions & 6 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ def load_translations
simple_form:
labels:
user:
age: Age
avatar: Avatar
age: Your Age
avatar: Your Avatar
awesome: Are you awesome?
bio: Biography
date_of_birth: Date of birth
email: Email
name: Display name
date_of_birth: Your Date of birth
likes: Your Likes
dislikes: Your Dislikes
email: Your Email
name: Your Display name
new:
password: Password
password: Your Password
phone: Phone Number
terms_of_service: I agree to the Terms of Service
url: Website
Expand Down

0 comments on commit 4258e08

Please sign in to comment.