Testing fields added dynamically by cocoon using rspec and capybara - ruby-on-rails

I was wondering whether anybody tests fields that were dynamically added by cocoon?
It's a great little time saver but all of the fields that are added dynamically have really long numerics added to the ID and name. This means that I have to skip testing that requires more than one (set of) field(s) on the page.

Afaik you could test for two things:
that the dynamic addition of the nested elements works
creating elements, filling it in and storing them in the database
So assume the relevant part of your view looks like this (default example):
#tasks
= f.semantic_fields_for :tasks do |task|
= render 'task_fields', :f => task
.links
= link_to_add_association 'add task', f, :tasks
and your nested element looks like
.nested-fields
= f.input :description
= f.input :done, :as => :boolean
= link_to_remove_association "remove task", f
So normally you give it a class, i normally just test the count of elements on the page.
So if one element is already there, creating a new element, the count should be two. This you could test with
find("#tasks .nested-fields").count.should == 2
Filling in the newly added nested element, you could use the :last-child css selector
find("#tasks .nested-fields:last-child input#description").set("something")
How names and id are formed, are close to rails internals, so i try to stay away of those.

Maybe using Capybara finders all, first and the selector input. Something like this:
visit new_resource_path
click_link "Add a Nested Resource"
first("input[name='nested_resource[name]']").set("Nested Resource")
click_button "submit"
Or
visit new_resource_path
click_link "Add a Nested Resource"
click_link "Add a Nested resource"
all("input[name='nested_resource[name]']").each do |input|
input.set("Nested Resource")
end
click_button "submit
This is only an approach, I've never worked with cocoon. This is however, a form to test dynamic inputs.

Strange.
i have in the form (haml):
= link_to_add_association f, :staff_phases, class: :button, id: 'add-staff-phase-link' do
%i.fas.fa-plus
and in my test
click_link 'add-staff-phase-link'
save_and_open_page
the click_link works but i can see no added association.
if i, then on the opened page, click the link by mouse, the cocoon-link works and adds a association

It might be common knowledge, but sharing this as it worked for me when dealing with a nested attribute that requires a select instead of a text input. I find all instances of my select fields, then pick the select field I want to updated with my preferred option like below:
page.find_all('select[id^="model_model_nested_attributes_"]')[2].select("preferred_option")

A possible alternate that I just used is to dynamically update the label of each added form field (using the technique mentioned in https://github.com/nathanvda/cocoon/issues/374) and now my Cucumber/Capybara tests can easily insert text into the different multiple form fields distinguishing them via the different labels they have.
Full details on what I did in this PR https://github.com/AgileVentures/WebsiteOne/pull/1818

Related

rails link_to tag to build join record in rails

I'm trying to build a form that allows a user to add products to an order.
The way I have it setup so far is that a user will select from 2 dropdown boxes and type into 1 text field
1 - the product they want
2 - its size
3 - the quantity they want.
What I hope to do is have the user click a link_to tag to "Add" this item to their order.
I was thinking I could do this via ajax and build the associative record in my controller and have it render on the page when the request returns.
When the user is done with their order and hits submit I can create my Customer Order with the products they wish to buy.
Am I approaching this correctly?
e.g. my form has the following:
<%= collection_select :order_line_item, :cake_id, Cake.order(:name), :id, :<%= grouped_collection_select :order_line_item, :cake_size_id, Cake.all, :cake_sizes, :name, :id, :name %>
<%= label_tag :quantity %>
<%= text_field_tag :quantity %>
<%= link_to "Add to order", add_to_order_path, {method: :post, remote: true} %>
Am I approaching this correctly? I then need to be able to add the fields above to the ajax post so I can populate the associative record with the relevant values.
Am I approaching this correctly?
I don't know about 'correctly'. But, I can imagine some alternatives.
Here are some sketches:
One Option:
This approach assumes that the Order is already saved so that you can associate a Product with that order. Perhaps Order has a status.
You could wrap that whole bit (product, size, quantity) in its own form (not embedded within your order form).
Have the form submit via js using remote: true (if you're using Rails 5, then this may be the default behavior).
When the user clicks on "Add", you will receive the field values as parameters in your controller where you can associate the Product with the Order.
Then, render back an HTML blob that can be inserted into the DOM (perhaps an order row?)
Use js to insert the blob and clear the form.
Another Option:
You could leave that whole bit (product, size, quantity) as not a form and have it reside outside your form.
Wrap it all up in a div.
Convert that link into a span or something similar.
Attach an .on 'click' event (I'm assuming jquery, you don't specify, so I'm going to run with it) to the wrapper.
When the link is clicked, the click event will bubble up to the wrapper.
Have the wrapper submit the field values via ajax.
Proceed as above.
I wouldn't really recommend this approach as it seems to me that you're basically replicating the functionality of a remote form. But, there is...
Yet Another Option
This approach does not require that the Order already exists.
You could have a hidden order item row outside of your form.
You construct your page as above in Another Option.
Now, when the user clicks the "Add" button, clone the hidden order item row.
Fill in the cloned order item with the appropriate values.
Insert the cloned order item into your Order form.
When the user clicks "Order" or "Submit" or whatever they click when they're done, you'll get all of the order rows as field sets.
Process the order line items along with the form. (Some folks might suggest accepts_nested_attributes_for, but I never use that.)
I suspect there are others. Or perhaps variations.

Why isn't Capybara filling in these form elements?

I have been trying to set up Capybara to test a form but I keep getting the error
cannot fill in, no text field, text area or password field with id, name, or label 'Name' found
Here is what I have in my view:
<%= form_for(#user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
...
<%= f.submit "Create Account", class: "btn btn-large btn-primary" %>
<% end %>
which renders the following html:
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" size="30" type="text" />
So it seems like it should be finding the field based on the label.
Here is what my user_pages_test.rb file has (I am using Test::Unit and shoulda-context):
context "sign up page" do
should "add user to database when fields are filled in" do
fill_in "Name", with: "Bubbles"
...
click_button "Create Account"
end
end
Here is what I've tried so far:
1) changing the call to fill_in to match the id with fill_in "user_name", with: "Bubbles"
2) changing the call to fill_in to page.fill_in "Name", with: "Bubbles" to match the example in the documentation
3) changing the view to manually add the id "Name" with <%= f.text_field :name, id: "Name" %> (this answer)
4) changing the call to get sign_up_path to get "/sign_up" (in case it was an issue with the routing)
All these still give me the same error, which makes me think that the page isn't being loaded correctly for some reason. However, I have a different (passing) test in the same context that asserts the page has the correct title, so I know the page does get loaded correctly (in the setup).
Based on this (and according to this answer), it seems like the problem might just be that the fill_in method isn't waiting for the page to load before trying to access the fields. According to this suggestion, I added the line puts page.body in my test to see that the HTML was being loaded completely before it was trying to fill in the fields, and got the following output:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
So, I was pretty sure that fill_in just wasn't waiting for the page to load. I then tried
5) changing the Capybara.default_wait_time according to this answer, but this had no effect. I tried setting it in the ActionDispatch::IntegrationTest class in the test_helper file (where Capybara is included), and also in the test itself.
Then I tried adding puts page.body in the passing test (both before and after it asserts the correct title), and I got the same output. THEN, I found this answer, and finally got the console to print out the page's HTML. I tried one more thing to get Capybara to fill in the fields:
6) changed the call to fill_in to say #response.fill_in, since #response seems to do what I thought the page variable was supposed to do.
So, I have two questions about this:
1) What does the page variable actually refer to? The doctype declaration for my app is just <!DOCTYPE html>, so I have no idea where it gets the old one from.
2) Why isn't Capybara able to find/fill_in these fields?
You need to use the visit method to get the page object setup properly. The capybara documentation indicates that '/' is visited by default and if you want to visit some other page you need to do an explicit call. It may also be helpful to read a bit about the difference between visit and get.

How to only display the name of an associated record in rails 3.1.0

There are rfq, quote and test_item in our rails 3.1.0 app. RFQ has many quotes and test items. Quote belongs to a rfq and has many test items. Test item has many rfqs and quotes.
When creating a quote, the test items in the rfq are passed into the quote new form directly. Here is the code in new in quotes controller:
#quote = #rfq.quotes.new()
#quote.test_items << #rfq.test_items
Here is the view code in quote new form for displaying the test items passed from the rfq:
<%= simple_form_for([#rfq, #quote]) do |f| %>
....
<% #quote.test_items.each do |t| %>
<p><%= f.association :test_items, :label => false %><%= link_to_remove_fields "remove", f %></p>
<% end %>
....
<% end %>
The view code above can display the test item passed from the rfq and maintain the association (quote has many test items) which is what the app needs. However it also displays a selection box for test item which is not needed here(can't change the test item in quote other than delete. Also t in the loop hasn't been useful). What we need is only to display the name of the test items, maintain the association (quote has many test items) and allow to delete test item (done by link_to_remove_fields).
Is there a clean way to accomplish this? Thanks so much.
Take a look at the nested attributes mechanism in Rails. It requires both a model declaration, accepts_nested_attributes_for and is supported by the fields_for form helpers that are also compatible with simple_form's simple_fields_for.
I think your error is that you loop over the test_items explicitly and call f.assocation which tells the form that you'll want to choose a different test_item. I think what you're trying to do is to let the user remove test_items with a check box. You'll need to use fields_for to implicitly loop over the list of test_items and use nested_attributes to permit deletion.

How to make the 'I fill in' step in Cucumber find a field w/o defining the label?

I am starting with Cucumber and I am running this simple scenario:
Scenario: Creating a project
Given I go to the new project page
And I fill in "Name" with "xxx"
...
The new project form is already in place and looks like this:
<% form_for (#project) do |f| %>
<%= f.text_field :name %>
...
<% end %>
As you can see I haven't defined a label (don't need one). The problem is that cucumber doesn't find the field:
Given I go to the new project page
And I fill in "Name" with "I need something" #
features/step_definitions/web_steps.rb:68
cannot fill in, no text field, text area or password field with id,
name, or label 'Name' found
(Capybara::ElementNotFound)
However it finds it if I add the label. I find it a little bit suspicious that it can't match the name I gave with the appropriate text field.
What is wrong?
Thank you
Try changing your cucumber feature to read:
Scenario: Creating a project
Given I go to the new project page
And I fill in "project_name" with "xxx"
Or...
Scenario: Creating a project
Given I go to the new project page
And I fill in "project[name]" with "xxx"
The reason this is failing is because if you have no label, capybara (which cucumber uses) looks for the ID or name attribute of the field. As apneadiving mentions, you can check these values by looking at the source of the generated file. You'll see something like:
<input id="project_name" name="project[name]" type="text">
The name of your textfield is by default:
project[name]
Actually it follows the rule:
object[field]
You should look at your dom and check it.
Edit:
Jus FYI, It's weird to see:
<% form_for (#project) do |f| %>
instead of:
<%= form_for (#project) do |f| %>
I had the same problem and I fixed it by simply changing
<% form_for ... %>
to
<%= for_for ... %>
The addition of the '=' enables the display of the form fields, which allows Capybara to correctly identify it.

Ruby on Rails 3, forms, ajax, nested, in-place editing, one-by-one. Best practice

Assume, that I have a complex nested form with many fields.
I want to edit its fields one-by-one in ajax way, so that every time I see form - it is in 'show' style (without fields to change information), but with possibility to switch any particular field or group of fields to 'edit' mode with it's own 'save' or 'update' button.
Solving this kind of problem, I ended up with two ways:
Extended use of Ryan Bates' complex-form-examples.
The disadvantage of this way is that every field (or group of fields) requires it's own code (i.e. javascript: 'insert_fields'), which renders corresponding 'edit' style form, so it results in page is overwhelmed by javascripts.
Second - is unified procedure of loading corresponding edit partials through ajax through special controller action (i.e. get_partial), which "render :do updates" given field's area by 'edit' form.
For given field or group of fields i have partials for 'edit' and for 'show'. When i need to switch that field to edit mode i send request (link_to ...,'/.../get_partial?partial=foo',:remote => true) with necessary params by ajax, and some controller#action renders that partial by javascript.
I think that second approach is better one, but I can't figure out how optimize it the best.
Are there any more elegant solutions to this problem?
What if you generated a normal 'edit' form (with all the nested fields, etc), and then had the javascript hide the fields and add the text of the field and an edit link next to it. So for example say your form looks like:
= form_for #foo do |f|
= f.text_field :name
and your javascript would do this to it (1):
= form_for #foo do |f|
= f.text_field :name, :class => "hide"
<the value of the field here>
= link_to "edit", "#"
then make your javascript add a click event to the edit links that, when clicked, does:
= form_for #foo do |f|
= f.text_field :name
= f.submit "Save"
then you'd need more javascript that makes the save button submit the form (ajax), and go back to (1) above

Resources