2
\$\begingroup\$

The following feature spec tests CRUD operations for the users resource in a Rails 4.2.8 application. It includes tests for filtering, sorting and pagination features of the index view.

The tests work. However, I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).

In the index view, the data is displayed within a table. In the show view, it is displayed within a definition list (<dl> tag).

# spec/features/user_crud_spec.rb
require "rails_helper"
def find_by_i18n_title(i18n_key)
 # Bootstrap renames the "title" attribute to "data-original-title"
 find("[data-original-title='#{I18n.t(i18n_key)}']")
end
# Select items from select2 dropdowns. https://select2.github.io/
def select_select2_option(option_text)
 first('.select2-container').click
 find('li', text: option_text).click
end
# Inspired by https://stackoverflow.com/a/45287911/6307730
def sort_order_regex(*sort_by_attributes)
 /#{User.order(sort_by_attributes)
 .map { |u| Regexp.quote(u.email) }
 .join(".+")}/
end
RSpec.shared_examples "sort link" do |link_text:, sort_by:|
 it "sorts by #{sort_by} when the #{link_text} link is clicked" do
 # Trigger the lazy creation of 2 more records, total count is now 3
 users
 visit admin_users_path
 # Check the record's order by matching the order of their e-mails (unique).
 initial_order = sort_order_regex(:first_name, :last_name)
 tested_order = sort_order_regex(sort_by)
 within_table "users_table" do
 expect(page).to have_text(initial_order)
 click_link(link_text, exact: false)
 expect(page).to have_text(tested_order)
 end
 end
end
RSpec::Matchers.define :have_single_record do |slug|
 match do |page|
 expect(page).to have_selector("tr.user", count: 1)
 expect(page).to have_selector("tr#user_#{slug}")
 end
 failure_message do |page|
 "expected to find a single record in the page with slug #{slug}. " \
 "Found #{page.all("tr#user_#{slug}").count} records instead."
 end
end
describe "User admin CRUD", type: :feature, js: true do
 let(:user) { FactoryGirl.create(:admin_user) }
 let(:user_with_associations) do
 user = FactoryGirl.create(:user)
 masters = FactoryGirl.create_pair(:master, users: [user])
 # Quote requires an associated user to be valid. The :quote factory creates
 # a user unless we pass an existing one as an argument.
 FactoryGirl.create(:quote, master: masters.first, user: user)
 user
 end
 let(:valid_attributes) { FactoryGirl.attributes_for(:user) }
 before(:each) { login_as(user) }
 describe "Index view" do
 let(:users) { FactoryGirl.create_pair(:user) << user }
 it "displays all existing users" do
 # Trigger the lazy creation of one more user with associations (to
 # ensure the associations appear in the index page)
 users = [user, user_with_associations]
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: users.count)
 users.each do |user|
 # <tr> must have user_x (x is the record slug) as DOM ID
 within("#user_#{user.slug}") do
 # OPTION 1
 expected_regex = /#{[
 user.email,
 user.role,
 user.full_name,
 CS.countries[user.country.to_sym],
 user.sangha,
 user.masters.map(&:name).join(' '),
 user.quotes_count
 ].join(".*")}/
 expect(page).to have_text expected_regex
 # OPTION 2
 # requires <td>s to have their corresponding attribute names as a DOM class
 # expect(page).to have_selector ".email", text: user.email
 # expect(page).to have_selector ".role", text: user.role
 # expect(page).to have_selector ".full_name", text: user.full_name
 # expect(page).to have_selector ".country",
 # text: CS.countries[user.country.to_sym]
 # expect(page).to have_selector ".sangha", text: user.sangha
 # expect(page).to have_selector ".masters",
 # text: user.masters.map(&:name).join(' ')
 # expect(page).to have_selector ".quotes_count",
 # text: user.quotes_count
 end
 end
 end
 it "loads the show view when the 'show' icon is clicked" do
 visit admin_users_path
 find_by_i18n_title("actions.show").click
 expect(page).to have_current_path(admin_user_path(user))
 end
 it "loads edit view when the 'edit' icon is clicked" do
 visit admin_users_path
 find_by_i18n_title("actions.edit").click
 expect(page).to have_current_path(edit_admin_user_path(user))
 end
 it "destroys the user when the 'delete' icon is clicked" do
 # Create a new user so the logged-in user does not get deleted
 user = FactoryGirl.create(:user)
 visit admin_users_path
 expect do
 within("#user_#{user.slug}") do
 page.accept_alert I18n.t("are_you_sure") do
 find_by_i18n_title("actions.delete").click
 end
 end
 # Redirect back to the index view after record is destroyed.
 # Must be inside the #expect block to ensure the DB operation is
 # finished before trying to count records.
 expect(page).to have_current_path(admin_users_path)
 end.to change(User, :count).by(-1)
 end
 describe "sorting" do
 include_examples "sort link", link_text: "E-mail", sort_by: :email
 include_examples "sort link", link_text: "Role", sort_by: :role
 include_examples "sort link", link_text: "Country", sort_by: :country
 include_examples "sort link", link_text: "Sangha", sort_by: :sangha
 include_examples "sort link", link_text: "Quotes", sort_by: :quotes_count
 end
 describe "filtering" do
 it "filters by email" do
 # Trigger the lazy creation of 2 additional records. E-mails are unique.
 users
 visit admin_users_path
 fill_in "q_email_cont", with: user.email
 click_button "email_search_submit"
 expect(page).to have_single_record(user.slug)
 # Test the filter's "Clear" button
 click_on "Clear"
 expect(page).to have_selector("tr.user", count: users.count)
 end
 it "filters by full_name" do
 # The logged in user (created by let!(:user)) has a different name
 user = FactoryGirl.create(:user,
 first_name: "Unique",
 last_name: "Name")
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: 2)
 fill_in "q_full_name_cont", with: "Unique Name"
 click_button "email_search_submit"
 expect(page).to have_single_record(user.slug)
 end
 it "filters by role" do
 # The logged in user (created by let!(:user)) has :admin role
 user = FactoryGirl.create(:user, role: :user)
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: 2)
 select "user", from: "q_role_eq"
 # It auto-submits
 expect(page).to have_single_record(user.slug)
 end
 it "filters by country" do
 # The country of the logged in user is not "TH" (see the factory)
 user = FactoryGirl.create(:user, country: "TH")
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: 2)
 select "Thailand", from: "q_country_eq"
 # It auto-submits
 expect(page).to have_single_record(user.slug)
 end
 it "filters by master (select box)" do
 # The logged in user (created by let!(:user)) has no masters
 master = FactoryGirl.create(:master)
 user = FactoryGirl.create(:user, masters: [master])
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: 2)
 select master.name, from: "q_masters_name_cont"
 # It auto-submits
 expect(page).to have_single_record(user.slug)
 end
 end
 describe "pagination" do
 before(:all) do
 # Temporarily reduce the limit of users per page to avoid having to
 # create a large number of records.
 @original_max_per_page = Kaminari.config.default_per_page
 Kaminari.config.default_per_page = 2
 end
 after(:all) do
 # Restore original pagination settings
 Kaminari.config.default_per_page = @original_max_per_page
 end
 context "with a single record" do
 it "does not paginate" do
 visit admin_users_path
 expect(page).to have_selector("tr.user", count: 1)
 expect(page).to have_no_selector("ul.pagination")
 end
 end
 context "with 3 records and maximum records per page set to 2" do
 before(:each) do
 # Trigger the lazy creation of 2 more records, total count is now 3
 users
 visit admin_users_path
 end
 it "displays 2 records in the 1st page " do
 expect(page).to have_selector("ul.pagination")
 expect(page).to have_selector("tr.user", count: 2)
 end
 it "displays 1 record in the 2nd page" do
 within "ul.pagination" do
 click_link "2"
 end
 expect(page).to have_current_path(admin_users_path + "?page=2")
 expect(page).to have_selector("tr.user", count: 1)
 end
 end
 end
 end
 describe "#new view" do
 it "creates a new user" do
 visit new_admin_user_path
 expect do
 # Implemented in spec/support/helpers/capybara_fill_user_fields.rb
 fill_user_fields(valid_attributes)
 click_button "Create User"
 # Keep this inside the expect block to ensure it waits until the new
 # record is created before recounting records.
 expect(page).to have_current_path(admin_user_path(User.last))
 end.to change(User, :count).by(1)
 expect(page).to have_text "successfully created."
 expect(page).to have_text "Name #{valid_attributes[:first_name]}"
 end
 end
 describe "#show view" do
 it "displays all public attributes" do
 # Create a record with all associations to ensure they appear on the
 # #show view
 user = user_with_associations
 visit admin_user_path(user)
 [
 "E-mail #{user.email}",
 "Role #{user.role}",
 "Full Name #{user.full_name}",
 "Sangha #{user.sangha}",
 "Country #{CS.countries[user.country]}",
 "State #{CS.states(user.country)[user.state]}",
 "City #{user.city}",
 "Phone Number #{user.phone_number}",
 "Facebook URL #{user.facebook_url}",
 "Masters #{user.masters.map(&:name).join(' ')}",
 "Quotes #{user.quotes_count.to_s}"
 ].each do |text|
 expect(page).to have_text text
 end
 end
 end
 describe "#edit view" do
 it "allows an admin to update all public attributes of the user" do
 # create two masters to be selected in the "Masters" multiselect box.
 masters = FactoryGirl.create_pair(:master)
 visit edit_admin_user_path(user)
 # Implemented in spec/support/helpers/capybara_fill_user_fields.rb
 fill_user_fields(valid_attributes)
 # Select 2 items from the "Masters" select box
 masters.each { |master| select_select2_option(master.name) }
 click_button "Update User"
 expect(page).to have_current_path(admin_user_path(User.last))
 expect(page).to have_text "successfully updated."
 expect(page).to have_text "Name #{valid_attributes[:first_name]}"
 end
 end
end
asked Jul 25, 2017 at 19:15
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.