WithForm
Your System Test's counterpart to form_with
Usage
Leverage Rails-generated <label>
values to submit <form>
elements in System
Tests.
The with_form
test helper
To add coverage to a form's fields that are generated by ActionView's
form_with
helper, fill them using with_form
:
class UserInteractsWithFormsTest < ApplicationSystemTestCase
include WithForm::TestHelpers
test "user signs in" do
visit new_session_path
with_form(scope: :session) do |form|
form.fill_in :email, with: "user@example.com"
form.fill_in :password, with: "secr3t"
form.check :remember_me
form.click_button
end
assert_text "Welcome back, user@example.com."
end
test "user makes a post" do
post = Post.new(title: "My First Post", tags: ["ruby", "testing"])
visit new_post_path
with_form(model: post) do |form|
form.fill_in :title
form.check :tags
form.click_button
end
assert_text "Created Post: My First Post."
end
test "user updates their profile" do
profile = Profile.create!
visit profile_path
with_form(model: profile) do |form|
form.fill_in :email, with: "updated.user@example.com"
form.select "NY", from: :state
form.click_button :update
end
assert_text "Your profile has been updated."
end
end
with_form
Options
The with_form
helper method accepts two styles of options:
-
scope:
- the internationalization scope key to use when translating Capybara's locator valuesWhen submitting a
<form>
through a call toform.click_button
, you can pass anaction
as the translation scope. Awith_form(scope:)
call will default to thesubmit
key when one is not specified. For instance:with_form(scope: :post) do |form| form.click_button end
This call will search for an
<input type="text">
or<button>
whosevalue
or text content is the String translated by thehelpers.submit.post.submit
key.That action can be overridden:
with_form(scope: :post) do |form| form.click_button :create end
-
model:
- an instance of anActiveModel::Model
orActiveRecord::Base
to be used to translate Capybara's locator values, and to populate the fields with an attribute's value.For example, assuming that a
Post
record has atitle
attribute:post = Post.new(title: "The Title") with_form(model: post) do |form| form.fill_in :title end
The call to
form.fill_in
will search for an<input>
element or a<textarea>
element that is labelled by a<label>
element whose value is translated from thehelpers.label.post.title
internationalization key. If that element exists, set its value to the value ofpost.title
(in this case,"The Title"
).An attribute's value can be overridden by providing a different value. For instance, assuming that a
Post
record has atitle
attribute:post = Post.create!(title: "Old Title") with_form(model: post) do |form| form.fill_in :title, with: "New Title" end
The call to
form.fill_in
will work much like the example above, with the exception that the providedwith:
option's value (in this case,"New Title"
) will take precedence over thepost.title
attribute's value (in this case,"Old Title"
).When submitting a
<form>
through a call toform.click_button
, you can pass anaction
as the translation scope. Awith_form(model:)
call will determine the translation key based on themodel
argument's persistence state.When a
model
instance is a new record, the key will usecreate
. For instance:post = Post.new with_form(model: post) do |form| form.click_button end
This call will search for an
<input type="text">
or<button>
whosevalue
or text content is the String translated by thehelpers.submit.post.create
key.That action can be overridden:
post = Post.new with_form(model: post) do |form| form.click_button :submit end
When a
model
instance is an existing persisted record, the key will useupdate
. For instance:post = Post.last with_form(model: post) do |form| form.click_button end
This call will search for an
<input type="text">
or<button>
whosevalue
or text content is the String translated by thehelpers.submit.post.update
key.That action can be overridden:
post = Post.last with_form(model: post) do |form| form.click_button :submit end
With the exception of #click_link
and #click_link_or_button
, the argument
yielded to with_form
supports all helper methods made available by
Capybara::Node::Actions
.
Those include:
attach_file(locator = nil, paths, make_visible: nil, **options)
check(locator, **options)
choose(locator, **options)
click_button(locator, nil, **options)
fill_in(locator, with: nil, **options)
select(value = nil, from: nil, **options)
uncheck(locator, **options)
unselect(value = nil, from: nil, **options)
check
and uncheck
support
The check
and uncheck
helpers can support a mixture of argument types and
use cases.
with_form(scope:)
When a call to with_form
is passed the scope:
option, the check
and
uncheck
helpers can accept both a String
argument, or an Array
argument
populated with String
values.
For example, consider the following features/new
template:
<%# app/views/features/new.html.erb %>
<%= form_with(scope: :features) do |form| %>
<%= form.label(:supported) %>
<%= form.check_box(:supported) %>
<%= form.label(:languages) %>
<%= form.collection_check_boxes(
:languages,
[
[ "Ruby", "ruby" ],
[ "JavaScript", "js" ],
],
:last,
:first,
) %>
<% end %>
There are two styles of <input type="checkbox">
elements
at-play in this template:
-
a singular
<input type="checkbox">
element that corresponds to aBoolean
-backedsupported
attribute, constructed byActionView::Helpers::FormBuilder#check_box
-
a collection of
<input type="checkbox">
elements that correspond to an association of relatedlanguage
models, constructed byActionView::Helpers::FormBuilder#collection_check_boxes
The corresponding check
and uncheck
method exposed by
WithForm::TestHelpers
can interact with both.
To check or checked the Boolean
-backed <input type="checkbox">
elements,
pass the attribute's name as a Symbol
:
with_form scope: :features do |form| %>
form.check :supported
form.uncheck :supported
end
To check or checked the Array
-backed <input type="checkbox">
elements,
pass the values as either an Array
of String
values, or a singular String
value:
with_form scope: :features do |form| %>
form.check ["Ruby", "JavaScript"]
form.uncheck "JavaScript"
end
with_form(model:)
When a call to with_form
is passed the model:
option, the check
and
uncheck
helpers can accept a String
argument, an Array
argument populated
with String
values, or a singular Symbol
argument.
For example, consider the following hypothetical models:
Next, consider rendering a <form>
element within the features/new
template:
<%# app/views/features/new.html.erb %>
<%= form_with(model: Feature.new) do |form| %>
<%= form.label(:supported) %>
<%= form.check_box(:supported) %>
<%= form.label(:language_ids) %>
<%= form.collection_check_boxes(
:language_ids,
Language.all,
:id,
:name,
) %>
<% end %>
There are two styles of <input type="checkbox">
elements
at-play in this template:
-
a singular
<input type="checkbox">
element that corresponds to aBoolean
-backedsupported
attribute, constructed byActionView::Helpers::FormBuilder#check_box
-
a collection of
<input type="checkbox">
elements that correspond to an association of relatedLanguage
models, constructed byActionView::Helpers::FormBuilder#collection_check_boxes
The corresponding check
and uncheck
method exposed by
WithForm::TestHelpers
can interact with both.
To check or checked the Boolean
-backed <input type="checkbox">
elements,
pass the attribute's name as a Symbol
:
with_form model: Feature.new(supported: false) do |form| %>
form.check :supported
form.uncheck :supported
end
To check or checked the Array
-backed <input type="checkbox">
elements,
pass the values as either an Array
of String
values, or a singular String
value:
feature = Feature.new(languages: Language.all, supported: true)
with_form model: feature do |form| %>
form.uncheck :supported
form.uncheck feature.languages.map(&:name)
form.check ["Ruby", "JavaScript"]
form.uncheck "JavaScript"
end
When interacting with the Boolean
-backed variation of the <input type="checkbox">
element through the form.check
or form.uncheck
calls, the
end-state of the <input>
element will always correspond to the variation
of check
or uncheck
.
More directly stated: calls to check
will always result in <input type="checkbox" checked>
, and calls to uncheck
will always result in
<input type="checkbox">
, regardless of the value of Feature#supported
.
If your intention is that the <input>
have the checked
attribute, call check
. If your intention is that the <input>
not have the checked
attribute, call uncheck
.
ActionText rich_text_area
support
When ActionText
is available, with_form
provides a
#fill_in_rich_text_area
helper method.
The current implementation is inspired by
ActionText::SystemTestHelper#fill_in_rich_text_area
that is currently declared
on the current rails@master
branch.
class UserInteractsWithRichTextAreasTest < ApplicationSystemTestCase
include WithForm::TestHelpers
test "makes a post with a scope: argument" do
visit new_post_path
with_form(scope: :post) do |form|
form.fill_in_rich_text_area :body, with: "My First Post"
form.click_button
end
assert_text "My First Post"
end
test "user makes a post with a model: argument" do
post = Post.new(body: "My First Post")
visit new_post_path
with_form(model: post) do |form|
form.fill_in_rich_text_area :body
form.click_button
end
assert_text "My First Post"
end
end
There is a current limitation in how the rails@master
-inspired
#fill_in_rich_text_area
implementation resolves the
locator
argument. Since the underlying <trix-editor>
element is not a
default field provided by the browser, focussing on its corresponding <label>
element won't focus the <trix-editor>
. To resolve that shortcoming, the
#fill_in_rich_text_area
uses the <trix-editor aria-label="...">
attribute as
the label text.
This is a helpful, but incomplete solution to the problem. This requires that
instead of declaring a <label for="my_rich_text_field">
element
referencing the <trix-editor id="my_rich_text_field">
element, the <label>
element's text (or rather, the text that would be in the <label>
element)
must be passed to the <trix-editor aria-label="...">
attribute.
For example:
<%= form_with(model: Post.new) do |form %>
<%= form.label :my_rich_text_field %>
<%= form.rich_text_area :my_rich_text_field, "aria-label": translate(:my_rich_text_field, scope: "helpers.label.post") %>
<% end %>
The label
and submit
test helpers
While with_form
can simplify <form>
element interactions with multiple
steps, there are times when a single line of instructions is more convenient.
Behind the scenes, with_form
utilize the #label
and #submit
helper
methods to translate <label>
and <button>
text, along with <input type="submit">
values.
To put the same helpers to use within your test, include the
WithForm::TranslationHelpers
module and invoke either:
-
label(model_name, attribute)
-
submit(model_name, action = :submit)
For example when clicking an <input type="checkbox">
labelled by a translation
declared at helpers.label.session.ready
:
class UserInteractsWithFormsTest < ApplicationSystemTestCase
include WithForm::TranslationHelpers
test "user ticks a box" do
visit new_session_path
check label(:session, :ready)
assert_text "We're glad you're ready, user@example.com."
end
end
Or, to destroy a Post by clicking a button labelled by a translation declared at
helpers.submit.post.destroy
:
class UserInteractsWithFormsTest < ApplicationSystemTestCase
include WithForm::TranslationHelpers
test "user deletes a post" do
visit new_post_path
click_on submit(:post, :destroy)
assert_text "Deleted Post."
end
end
Installation
Add this line to your application's Gemfile:
gem 'with_form'
And then execute:
$ bundle
Then, include the WithForm::TestHelpers
into your project testing framework.
MiniTest
# test/application_system_test_case.rb
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
include WithForm::TestHelpers
end
RSpec
# spec/support/with_form.rb
RSpec.configure do |config|
config.include(WithForm::TestHelpers, type: :system)
end
FAQ
I want to call a Capybara helper with that input's id
attribute
or name
attribute. How can I do that?
-
You can mix the object that you invoke the helper methods on within the
with_form
block. For instance:with_form(scope: :post) do |form| form.fill_in :title, with: "The Post's Title" fill_in "another-field-id", with: "Another Value" fill_in "post[special-field]", with: "Special Value" end
I've used the formulaic
gem before. How is this gem different?
- Formulaic's
fill_form
andfill_form_and_submit
are very useful abstractions oversimple_form
-generated<form>
elements. This gem's focus is at a different layer of abstraction. Instead of translating a Hash of attribute-value pairs into<form>
element interactions, this gem's interface focussed on enhancing the experience of filling inform_with
-generated<form>
elements that are labelled by theActionView
-provided internationalization tooling.
Contributing
See CONTRIBUTING.md.
License
The gem is available as open source under the terms of the MIT License.