diff --git a/Gemfile b/Gemfile index f2895387e..303014731 100644 --- a/Gemfile +++ b/Gemfile @@ -13,8 +13,6 @@ gem "pg", "~> 1.1" gem "puma", "~> 5.0" # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem "webpacker", "~> 5.0" -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem "jbuilder", "~> 2.7" # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", ">= 1.4.4", require: false # Gov.UK frontend components @@ -61,7 +59,7 @@ end group :test do gem "capybara", require: false - gem "database_cleaner-active_record", require: false + gem "capybara-lockstep" gem "factory_bot_rails" gem "selenium-webdriver", require: false gem "simplecov", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 48f6e5971..300874dd8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,7 +26,7 @@ GIT GIT remote: https://github.com/rspec/rspec-rails.git - revision: d3e7b85877fcbcec63f8a76434d8750e7f3b7aef + revision: 1fe6c2e8a56f46ae4c2f13ffa0733cce3b717c2d branch: main specs: rspec-rails (5.1.0.pre) @@ -124,7 +124,7 @@ GEM ast (2.4.2) bcrypt (3.1.16) bindex (0.8.1) - bootsnap (1.9.1) + bootsnap (1.9.3) msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) @@ -137,15 +137,16 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + capybara-lockstep (1.1.0) + activesupport (>= 3.2) + capybara (>= 2.0) + ruby2_keywords + selenium-webdriver (>= 3) chartkick (4.1.2) childprocess (4.1.0) coderay (1.1.3) concurrent-ruby (1.1.9) crass (1.0.6) - database_cleaner-active_record (2.0.1) - activerecord (>= 5.a) - database_cleaner-core (~> 2.0.0) - database_cleaner-core (2.0.1) deep_merge (1.2.1) devise (4.8.0) bcrypt (~> 3.0) @@ -171,7 +172,7 @@ GEM formtastic (4.0.0) actionpack (>= 5.2.0) formtastic_i18n (0.7.0) - globalid (0.5.2) + globalid (0.6.0) activesupport (>= 5.0) govuk-components (2.1.4) activemodel (>= 6.0) @@ -197,8 +198,6 @@ GEM railties (>= 5.2, < 6.2) responders (>= 2, < 4) iniparse (1.5.0) - jbuilder (2.11.3) - activesupport (>= 5.0.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -345,7 +344,7 @@ GEM sass (~> 3.5, >= 3.5.5) scss_lint-govuk (0.2.0) scss_lint - selenium-webdriver (4.0.3) + selenium-webdriver (4.1.0) childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) @@ -359,14 +358,14 @@ GEM sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.4.0) + sprockets-rails (3.4.1) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - stimulus-rails (0.7.2) + stimulus-rails (0.7.3) rails (>= 6.0.0) thor (1.1.0) - turbo-rails (0.8.3) + turbo-rails (0.9.0) rails (>= 6.0.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) @@ -404,8 +403,8 @@ DEPENDENCIES bootsnap (>= 1.4.4) byebug capybara + capybara-lockstep chartkick - database_cleaner-active_record devise discard dotenv-rails @@ -413,7 +412,6 @@ DEPENDENCIES govuk-components govuk_design_system_formbuilder hotwire-rails - jbuilder (~> 2.7) json-schema listen (~> 3.3) overcommit (>= 0.37.0) diff --git a/app/javascript/controllers/soft_validations_controller.js b/app/javascript/controllers/soft_validations_controller.js index 2c223ce1c..963dd7a76 100644 --- a/app/javascript/controllers/soft_validations_controller.js +++ b/app/javascript/controllers/soft_validations_controller.js @@ -5,13 +5,8 @@ export default class extends Controller { initialize() { let url = window.location.href + "/soft_validations" - this.fetch_retry(url, { headers: { accept: "application/json" } }, 5) - } - - fetch_retry(url, options, n) { - let self = this let div = this.overrideTarget - fetch(url, options) + fetch(url, { headers: { accept: "application/json" } }) .then(response => response.json()) .then((response) => { if(response["show"]){ @@ -27,10 +22,7 @@ export default class extends Controller { button.checked = false }) } - }) - .catch(function(error) { - if (n === 1) throw error - return self.fetch_retry(url, options, n - 1) - }) + } + ) } } diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 9db96bdfd..35150f279 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -113,6 +113,7 @@ class CaseLog < ApplicationRecord enum first_time_property_let_as_social_housing: DbEnums.polar, _suffix: true enum unitletas: DbEnums.unitletas, _suffix: true enum builtype: DbEnums.builtype, _suffix: true + enum incref: DbEnums.polar, _suffix: true AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze OPTIONAL_FIELDS = %w[do_you_know_the_postcode diff --git a/app/models/form/question.rb b/app/models/form/question.rb index c4fe5284b..e71e2f6d9 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -67,11 +67,7 @@ private operator = condition[:cond][/[<>=]+/].to_sym operand = condition[:cond][/\d+/].to_i case_log[condition[:from]].present? && case_log[condition[:from]].send(operator, operand) - when "text" - case_log[condition[:from]].present? && condition[:cond].include?(case_log[condition[:from]]) - when "radio" - case_log[condition[:from]].present? && condition[:cond].include?(case_log[condition[:from]]) - when "select" + when "text", "radio", "select" case_log[condition[:from]].present? && condition[:cond].include?(case_log[condition[:from]]) else raise "Not implemented yet" diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 34e97e8b9..a4073d259 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -15,6 +15,7 @@ <%= favicon_link_tag asset_pack_path('media/images/govuk-apple-touch-icon-180x180.png'), rel: 'apple-touch-icon', type: 'image/png', size: '180x180' %> <%= stylesheet_pack_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application', defer: true %> + <%= capybara_lockstep if defined?(Capybara::Lockstep) %>

<%= notice %>

<%= alert %>

diff --git a/spec/controllers/admin/admin_users_controller_spec.rb b/spec/controllers/admin/admin_users_controller_spec.rb index 33722cff4..73f339ac4 100644 --- a/spec/controllers/admin/admin_users_controller_spec.rb +++ b/spec/controllers/admin/admin_users_controller_spec.rb @@ -27,4 +27,16 @@ describe Admin::AdminUsersController, type: :controller do expect { post :create, session: valid_session, params: params }.to change(AdminUser, :count).by(1) end end + + describe "Update admin users" do + before do + get :edit, session: valid_session, params: { id: AdminUser.first.id } + end + + it "creates a new admin users" do + expect(page).to have_field("admin_user_email") + expect(page).to have_field("admin_user_password") + expect(page).to have_field("admin_user_password_confirmation") + end + end end diff --git a/spec/factories/admin_user.rb b/spec/factories/admin_user.rb index 083e24450..29a5b079b 100644 --- a/spec/factories/admin_user.rb +++ b/spec/factories/admin_user.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :admin_user do - email { "admin@example.com" } + sequence(:email) { |i| "admin#{i}@example.com" } password { "pAssword1" } created_at { Time.zone.now } updated_at { Time.zone.now } diff --git a/spec/factories/case_log.rb b/spec/factories/case_log.rb index ee791b1fc..944be4237 100644 --- a/spec/factories/case_log.rb +++ b/spec/factories/case_log.rb @@ -1,6 +1,5 @@ FactoryBot.define do factory :case_log do - sequence(:id) { |i| i } trait :in_progress do status { 1 } tenant_code { "TH356" } @@ -130,7 +129,7 @@ FactoryBot.define do mrcday { 5 } mrcmonth { 5 } mrcyear { 2020 } - incref { 554_355 } + incref { 0 } sale_completion_date { nil } startdate { Time.zone.now } armedforces { 1 } diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 41a613b38..c9f427a98 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :user do - email { "test@example.com" } + sequence(:email) { |i| "test#{i}@example.com" } password { "pAssword1" } created_at { Time.zone.now } updated_at { Time.zone.now } diff --git a/spec/features/case_log_spec.rb b/spec/features/case_log_spec.rb deleted file mode 100644 index f856aa3ba..000000000 --- a/spec/features/case_log_spec.rb +++ /dev/null @@ -1,508 +0,0 @@ -require "rails_helper" -RSpec.describe "Form Features" do - let!(:case_log) { FactoryBot.create(:case_log, :in_progress) } - let!(:empty_case_log) { FactoryBot.create(:case_log) } - let(:id) { case_log.id } - let(:status) { case_log.status } - - before do - allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) - end - - question_answers = { - tenant_code: { type: "text", answer: "BZ737", path: "tenant_code" }, - age1: { type: "numeric", answer: 25, path: "person_1_age" }, - sex1: { type: "radio", answer: "Female", path: "person_1_gender" }, - other_hhmemb: { type: "numeric", answer: 2, path: "household_number_of_other_members" }, - } - - def fill_in_number_question(case_log_id, question, value, path) - visit("/case_logs/#{case_log_id}/#{path}") - fill_in("case-log-#{question.to_s.dasherize}-field", with: value) - click_button("Save and continue") - end - - def answer_all_questions_in_income_subsection - visit("/case_logs/#{empty_case_log.id}/net_income") - fill_in("case-log-earnings-field", with: 18_000) - choose("case-log-incfreq-yearly-field") - click_button("Save and continue") - choose("case-log-benefits-all-field") - click_button("Save and continue") - choose("case-log-hb-prefer-not-to-say-field") - click_button("Save and continue") - end - - describe "Create new log" do - it "redirects to the task list for the new log" do - visit("/case_logs") - click_link("Create new log") - id = CaseLog.order(created_at: :desc).first.id - expect(page).to have_content("Tasklist for log #{id}") - end - end - - describe "Viewing a log" do - context "tasklist page" do - it "displays a tasklist header" do - visit("/case_logs/#{id}") - expect(page).to have_content("Tasklist for log #{id}") - expect(page).to have_content("This submission is #{status.humanize.downcase}") - end - - it "displays a section status" do - visit("/case_logs/#{empty_case_log.id}") - - assert_selector ".govuk-tag", text: /Not started/, count: 8 - assert_selector ".govuk-tag", text: /Completed/, count: 0 - assert_selector ".govuk-tag", text: /Cannot start yet/, count: 1 - end - - it "shows the correct status if one section is completed" do - answer_all_questions_in_income_subsection - visit("/case_logs/#{empty_case_log.id}") - - assert_selector ".govuk-tag", text: /Not started/, count: 7 - assert_selector ".govuk-tag", text: /Completed/, count: 1 - assert_selector ".govuk-tag", text: /Cannot start yet/, count: 1 - end - - it "skips to the first section if no answers are completed" do - visit("/case_logs/#{empty_case_log.id}") - expect(page).to have_link("Skip to next incomplete section", href: /#household_characteristics/) - end - - it "shows the number of completed sections if no sections are completed" do - visit("/case_logs/#{empty_case_log.id}") - expect(page).to have_content("You've completed 0 of 9 sections.") - end - - it "shows the number of completed sections if one section is completed" do - answer_all_questions_in_income_subsection - visit("/case_logs/#{empty_case_log.id}") - expect(page).to have_content("You've completed 1 of 9 sections.") - end - end - - describe "form questions" do - let(:case_log_with_checkbox_questions_answered) do - FactoryBot.create( - :case_log, :in_progress, - housingneeds_a: "Yes", - housingneeds_c: "Yes" - ) - end - - it "can be accessed by url" do - visit("/case_logs/#{id}/person_1_age") - expect(page).to have_field("case-log-age1-field") - end - - it "updates model attributes correctly for each question" do - question_answers.each do |question, hsh| - type = hsh[:type] - answer = hsh[:answer] - path = hsh[:path] - original_value = case_log.send(question) - visit("/case_logs/#{id}/#{path}") - case type - when "text" - fill_in("case-log-#{question.to_s.dasherize}-field", with: answer) - when "radio" - choose("case-log-#{question.to_s.dasherize}-#{answer.parameterize}-field") - else - fill_in("case-log-#{question.to_s.dasherize}-field", with: answer) - end - expect { click_button("Save and continue") }.to change { - case_log.reload.send(question.to_s) - }.from(original_value).to(answer) - end - end - - it "updates total value of the rent", js: true do - visit("/case_logs/#{id}/rent") - - fill_in("case-log-brent-field", with: 3) - expect(page).to have_field("case-log-tcharge-field", with: "3") - - fill_in("case-log-scharge-field", with: 2) - expect(page).to have_field("case-log-tcharge-field", with: "5") - - fill_in("case-log-pscharge-field", with: 1) - expect(page).to have_field("case-log-tcharge-field", with: "6") - - fill_in("case-log-supcharg-field", with: 4) - expect(page).to have_field("case-log-tcharge-field", with: "10") - end - - it "displays number answers in inputs if they are already saved" do - visit("/case_logs/#{id}/property_postcode") - expect(page).to have_field("case-log-property-postcode-field", with: "P0 5ST") - end - - it "displays text answers in inputs if they are already saved" do - visit("/case_logs/#{id}/person_1_age") - expect(page).to have_field("case-log-age1-field", with: "17") - end - - it "displays checkbox answers in inputs if they are already saved" do - visit("/case_logs/#{case_log_with_checkbox_questions_answered.id}/accessibility_requirements") - # Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we pass false here - expect(page).to have_checked_field( - "case-log-accessibility-requirements-housingneeds-a-field", - visible: false, - ) - expect(page).to have_unchecked_field( - "case-log-accessibility-requirements-housingneeds-b-field", - visible: false, - ) - expect(page).to have_checked_field( - "case-log-accessibility-requirements-housingneeds-c-field", - visible: false, - ) - end - end - - describe "Back link directs correctly", js: true do - it "go back to tasklist page from tenant code" do - visit("/case_logs/#{id}") - visit("/case_logs/#{id}/tenant_code") - click_link(text: "Back") - expect(page).to have_content("Tasklist for log #{id}") - end - - it "go back to tenant code page from tenant age page", js: true do - visit("/case_logs/#{id}/tenant_code") - click_button("Save and continue") - visit("/case_logs/#{id}/person_1_age") - click_link(text: "Back") - expect(page).to have_field("case-log-tenant-code-field") - end - - it "doesn't get stuck in infinite loops", js: true do - visit("/case_logs") - visit("/case_logs/#{id}/net_income") - fill_in("case-log-earnings-field", with: 740) - choose("case-log-incfreq-weekly-field", allow_label_click: true) - click_button("Save and continue") - click_link(text: "Back") - click_link(text: "Back") - expect(page).to have_current_path("/case_logs") - end - end - end - - describe "Form flow is correct" do - context "given an ordered list of pages" do - it "leads to the next one in the correct order" do - pages = question_answers.map { |_key, val| val[:path] } - pages[0..-2].each_with_index do |val, index| - visit("/case_logs/#{id}/#{val}") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/#{pages[index + 1]}") - end - end - - context "when changing an answer from the check answers page", js: true do - it "the back button routes correctly" do - visit("/case_logs/#{id}/household_characteristics/check_answers") - first("a", text: /Answer/).click - click_link("Back") - expect(page).to have_current_path("/case_logs/#{id}/household_characteristics/check_answers") - end - end - end - end - - describe "check answers page" do - let(:subsection) { "household_characteristics" } - let(:conditional_subsection) { "conditional_question" } - - context "when the user needs to check their answers for a subsection" do - it "can be visited by URL" do - visit("case_logs/#{id}/#{subsection}/check_answers") - expect(page).to have_content("Check the answers you gave for #{subsection.tr('_', ' ')}") - end - - let(:last_question_for_subsection) { "household_number_of_other_members" } - it "redirects to the check answers page when answering the last question and clicking save and continue" do - fill_in_number_question(id, "other_hhmemb", 0, last_question_for_subsection) - expect(page).to have_current_path("/case_logs/#{id}/#{subsection}/check_answers") - end - - it "has question headings based on the subsection" do - visit("case_logs/#{id}/#{subsection}/check_answers") - question_labels = ["Tenant code", "Tenant's age", "Tenant's gender", "Number of Other Household Members"] - question_labels.each do |label| - expect(page).to have_content(label) - end - end - - it "should display answers given by the user for the question in the subsection" do - fill_in_number_question(empty_case_log.id, "age1", 28, "person_1_age") - choose("case-log-sex1-non-binary-field") - click_button("Save and continue") - visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") - expect(page).to have_content("28") - expect(page).to have_content("Non-binary") - end - - it "should have an answer link for questions missing an answer" do - visit("case_logs/#{empty_case_log.id}/#{subsection}/check_answers") - assert_selector "a", text: /Answer\z/, count: 4 - assert_selector "a", text: "Change", count: 0 - expect(page).to have_link("Answer", href: "/case_logs/#{empty_case_log.id}/person_1_age") - end - - it "should have a change link for answered questions" do - fill_in_number_question(empty_case_log.id, "age1", 28, "person_1_age") - visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") - assert_selector "a", text: /Answer\z/, count: 3 - assert_selector "a", text: "Change", count: 1 - expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/person_1_age") - end - - it "should have a change link for answered questions" do - visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers") - assert_selector "a", text: /Answer\z/, count: 4 - assert_selector "a", text: "Change", count: 0 - visit("/case_logs/#{empty_case_log.id}/accessibility_requirements") - check("case-log-accessibility-requirements-housingneeds-c-field") - click_button("Save and continue") - visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers") - assert_selector "a", text: /Answer\z/, count: 3 - assert_selector "a", text: "Change", count: 1 - expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/accessibility_requirements") - end - - it "should have a link pointing to the first question if no questions are answered" do - visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") - expect(page).to have_content("You answered 0 of 4 questions") - expect(page).to have_link("Answer the missing questions", href: "/case_logs/#{empty_case_log.id}/tenant_code") - end - - it "should have a link pointing to the next empty question if some questions are answered" do - fill_in_number_question(empty_case_log.id, "earnings", 18_000, "net_income") - - visit("/case_logs/#{empty_case_log.id}/income_and_benefits/check_answers") - expect(page).to have_content("You answered 1 of 4 questions") - expect(page).to have_link("Answer the missing questions", href: "/case_logs/#{empty_case_log.id}/net_income") - end - - it "should not display the missing answer questions link if all questions are answered" do - answer_all_questions_in_income_subsection - expect(page).to have_content("You answered all the questions") - assert_selector "a", text: "Answer the missing questions", count: 0 - end - - it "does not display conditional questions that were not visited" do - visit("case_logs/#{id}/#{conditional_subsection}/check_answers") - question_labels = ["Has the condition been met?"] - question_labels.each do |label| - expect(page).to have_content(label) - end - - excluded_question_labels = ["Has the next condition been met?", "Has the condition not been met?"] - excluded_question_labels.each do |label| - expect(page).not_to have_content(label) - end - end - - it "displays conditional question that were visited" do - visit("/case_logs/#{id}/conditional_question") - choose("case-log-preg-occ-no-field") - click_button("Save and continue") - visit("/case_logs/#{id}/#{conditional_subsection}/check_answers") - question_labels = ["Has the condition been met?", "Has the condition not been met?"] - question_labels.each do |label| - expect(page).to have_content(label) - end - - excluded_question_labels = ["Has the next condition been met?"] - excluded_question_labels.each do |label| - expect(page).not_to have_content(label) - end - end - end - end - - describe "Conditional questions" do - context "given a page where some questions are only conditionally shown, depending on how you answer the first question" do - it "initially hides conditional questions" do - visit("/case_logs/#{id}/armed_forces") - expect(page).not_to have_selector("#armed_forces_injured_div") - end - - it "shows conditional questions if the required answer is selected and hides it again when a different answer option is selected", js: true do - visit("/case_logs/#{id}/armed_forces") - # Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we allow label click here - choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true) - expect(page).to have_selector("#reservist_div") - choose("case-log-reservist-no-field", allow_label_click: true) - expect(page).to have_checked_field("case-log-reservist-no-field", visible: false) - choose("case-log-armedforces-no-field", allow_label_click: true) - expect(page).not_to have_selector("#reservist_div") - choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true) - expect(page).to have_unchecked_field("case-log-reservist-no-field", visible: false) - end - end - end - - describe "Question validation" do - context "given an invalid tenant age" do - it " of less than 0 it shows validation" do - visit("/case_logs/#{id}/person_1_age") - fill_in_number_question(empty_case_log.id, "age1", -5, "person_1_age") - expect(page).to have_selector("#error-summary-title") - expect(page).to have_selector("#case-log-age1-error") - expect(page).to have_selector("#case-log-age1-field-error") - end - - it " of greater than 120 it shows validation" do - visit("/case_logs/#{id}/person_1_age") - fill_in_number_question(empty_case_log.id, "age1", 121, "person_1_age") - expect(page).to have_selector("#error-summary-title") - expect(page).to have_selector("#case-log-age1-error") - expect(page).to have_selector("#case-log-age1-field-error") - end - end - end - - describe "Soft Validation" do - context "given a weekly net income that is above the expected amount for the given economic status but below the hard max" do - let(:case_log) { FactoryBot.create(:case_log, :in_progress, ecstat1: "Full-time - 30 hours or more") } - let(:income_over_soft_limit) { 750 } - let(:income_under_soft_limit) { 700 } - - it "prompts the user to confirm the value is correct", js: true do - visit("/case_logs/#{case_log.id}/net_income") - fill_in("case-log-earnings-field", with: income_over_soft_limit) - choose("case-log-incfreq-weekly-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_content("Are you sure this is correct?") - check("case-log-override-net-income-validation-override-net-income-validation-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{case_log.id}/net_income_uc_proportion") - end - - it "does not require confirming the value if the value is amended" do - visit("/case_logs/#{case_log.id}/net_income") - fill_in("case-log-earnings-field", with: income_over_soft_limit) - choose("case-log-incfreq-weekly-field", allow_label_click: true) - click_button("Save and continue") - fill_in("case-log-earnings-field", with: income_under_soft_limit) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{case_log.id}/net_income_uc_proportion") - case_log.reload - expect(case_log.override_net_income_validation).to be_nil - end - - it "clears the confirmation question if the amount was amended and the page is returned to using the back button", js: true do - visit("/case_logs/#{case_log.id}/net_income") - fill_in("case-log-earnings-field", with: income_over_soft_limit) - choose("case-log-incfreq-weekly-field", allow_label_click: true) - click_button("Save and continue") - fill_in("case-log-earnings-field", with: income_under_soft_limit) - click_button("Save and continue") - click_link(text: "Back") - expect(page).to have_no_content("Are you sure this is correct?") - end - - it "does not clear the confirmation question if the page is returned to using the back button and the amount is still over the soft limit", js: true do - visit("/case_logs/#{case_log.id}/net_income") - fill_in("case-log-earnings-field", with: income_over_soft_limit) - choose("case-log-incfreq-weekly-field", allow_label_click: true) - click_button("Save and continue") - check("case-log-override-net-income-validation-override-net-income-validation-field", allow_label_click: true) - click_button("Save and continue") - click_link(text: "Back") - expect(page).to have_content("Are you sure this is correct?") - end - end - end - - describe "conditional page routing", js: true do - before do - allow_any_instance_of(CaseLogValidator).to receive(:validate_pregnancy).and_return(true) - end - - it "can route the user to a different page based on their answer on the current page" do - visit("case_logs/#{id}/conditional_question") - # using a question name that is already in the db to avoid - # having to add a new column to the db for this test - choose("case-log-preg-occ-yes-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/conditional_question_yes_page") - click_link(text: "Back") - expect(page).to have_current_path("/case_logs/#{id}/conditional_question") - choose("case-log-preg-occ-no-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page") - end - - it "can route based on multiple conditions" do - visit("/case_logs/#{id}/person_1_gender") - choose("case-log-sex1-female-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/household_number_of_other_members") - visit("/case_logs/#{id}/conditional_question") - choose("case-log-preg-occ-no-field", allow_label_click: true) - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/conditional_question/check_answers") - end - end - - describe "date validation", js: true do - def fill_in_date(case_log_id, question, day, month, year, path) - visit("/case_logs/#{case_log_id}/#{path}") - fill_in("#{question}_1i", with: year) - fill_in("#{question}_2i", with: month) - fill_in("#{question}_3i", with: day) - end - it "does not allow out of range dates to be submitted" do - fill_in_date(id, "case_log_mrcdate", 3100, 12, 2000, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - - fill_in_date(id, "case_log_mrcdate", 12, 1, 20_000, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - - fill_in_date(id, "case_log_mrcdate", 13, 100, 2020, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - - fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers") - end - - it "does not allow non numeric inputs to be submitted" do - fill_in_date(id, "case_log_mrcdate", "abc", "de", "ff", "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - end - - it "does not allow partial inputs to be submitted" do - fill_in_date(id, "case_log_mrcdate", 21, 12, nil, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - - fill_in_date(id, "case_log_mrcdate", 12, nil, 2000, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - - fill_in_date(id, "case_log_mrcdate", nil, 10, 2020, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") - end - - it "allows valid inputs to be submitted" do - fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs") - click_button("Save and continue") - expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers") - end - end -end diff --git a/spec/features/form/check_answers_page_spec.rb b/spec/features/form/check_answers_page_spec.rb new file mode 100644 index 000000000..2da1982a2 --- /dev/null +++ b/spec/features/form/check_answers_page_spec.rb @@ -0,0 +1,123 @@ +require "rails_helper" +require_relative "helpers" + +RSpec.describe "Form Check Answers Page" do + include Helpers + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:empty_case_log) { FactoryBot.create(:case_log) } + let(:id) { case_log.id } + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + let(:subsection) { "household_characteristics" } + let(:conditional_subsection) { "conditional_question" } + + context "when the user needs to check their answers for a subsection" do + it "can be visited by URL" do + visit("case_logs/#{id}/#{subsection}/check_answers") + expect(page).to have_content("Check the answers you gave for #{subsection.tr('_', ' ')}") + end + + let(:last_question_for_subsection) { "household_number_of_other_members" } + it "redirects to the check answers page when answering the last question and clicking save and continue" do + fill_in_number_question(id, "other_hhmemb", 0, last_question_for_subsection) + expect(page).to have_current_path("/case_logs/#{id}/#{subsection}/check_answers") + end + + it "has question headings based on the subsection" do + visit("case_logs/#{id}/#{subsection}/check_answers") + question_labels = ["Tenant code", "Tenant's age", "Tenant's gender", "Number of Other Household Members"] + question_labels.each do |label| + expect(page).to have_content(label) + end + end + + it "should display answers given by the user for the question in the subsection" do + fill_in_number_question(empty_case_log.id, "age1", 28, "person_1_age") + choose("case-log-sex1-non-binary-field") + click_button("Save and continue") + visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") + expect(page).to have_content("28") + expect(page).to have_content("Non-binary") + end + + it "should have an answer link for questions missing an answer" do + visit("case_logs/#{empty_case_log.id}/#{subsection}/check_answers") + assert_selector "a", text: /Answer\z/, count: 4 + assert_selector "a", text: "Change", count: 0 + expect(page).to have_link("Answer", href: "/case_logs/#{empty_case_log.id}/person_1_age") + end + + it "should have a change link for answered questions" do + fill_in_number_question(empty_case_log.id, "age1", 28, "person_1_age") + visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") + assert_selector "a", text: /Answer\z/, count: 3 + assert_selector "a", text: "Change", count: 1 + expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/person_1_age") + end + + it "should have a change link for answered questions" do + visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers") + assert_selector "a", text: /Answer\z/, count: 4 + assert_selector "a", text: "Change", count: 0 + visit("/case_logs/#{empty_case_log.id}/accessibility_requirements") + check("case-log-accessibility-requirements-housingneeds-c-field") + click_button("Save and continue") + visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers") + assert_selector "a", text: /Answer\z/, count: 3 + assert_selector "a", text: "Change", count: 1 + expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/accessibility_requirements") + end + + it "should have a link pointing to the first question if no questions are answered" do + visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers") + expect(page).to have_content("You answered 0 of 4 questions") + expect(page).to have_link("Answer the missing questions", href: "/case_logs/#{empty_case_log.id}/tenant_code") + end + + it "should have a link pointing to the next empty question if some questions are answered" do + fill_in_number_question(empty_case_log.id, "earnings", 18_000, "net_income") + + visit("/case_logs/#{empty_case_log.id}/income_and_benefits/check_answers") + expect(page).to have_content("You answered 1 of 4 questions") + expect(page).to have_link("Answer the missing questions", href: "/case_logs/#{empty_case_log.id}/net_income") + end + + it "should not display the missing answer questions link if all questions are answered" do + answer_all_questions_in_income_subsection(empty_case_log) + expect(page).to have_content("You answered all the questions") + assert_selector "a", text: "Answer the missing questions", count: 0 + end + + it "does not display conditional questions that were not visited" do + visit("case_logs/#{id}/#{conditional_subsection}/check_answers") + question_labels = ["Has the condition been met?"] + question_labels.each do |label| + expect(page).to have_content(label) + end + + excluded_question_labels = ["Has the next condition been met?", "Has the condition not been met?"] + excluded_question_labels.each do |label| + expect(page).not_to have_content(label) + end + end + + it "displays conditional question that were visited" do + visit("/case_logs/#{id}/conditional_question") + choose("case-log-preg-occ-no-field") + click_button("Save and continue") + visit("/case_logs/#{id}/#{conditional_subsection}/check_answers") + question_labels = ["Has the condition been met?", "Has the condition not been met?"] + question_labels.each do |label| + expect(page).to have_content(label) + end + + excluded_question_labels = ["Has the next condition been met?"] + excluded_question_labels.each do |label| + expect(page).not_to have_content(label) + end + end + end +end diff --git a/spec/features/form/conditional_questions_spec.rb b/spec/features/form/conditional_questions_spec.rb new file mode 100644 index 000000000..dc6193fa2 --- /dev/null +++ b/spec/features/form/conditional_questions_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe "Form Conditional Questions" do + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:id) { case_log.id } + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + context "given a page where some questions are only conditionally shown, depending on how you answer the first question" do + it "initially hides conditional questions" do + visit("/case_logs/#{id}/armed_forces") + expect(page).not_to have_selector("#armed_forces_injured_div") + end + + it "shows conditional questions if the required answer is selected and hides it again when a different answer option is selected", js: true do + visit("/case_logs/#{id}/armed_forces") + # Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we allow label click here + choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true) + expect(page).to have_selector("#reservist_div") + choose("case-log-reservist-no-field", allow_label_click: true) + expect(page).to have_checked_field("case-log-reservist-no-field", visible: false) + choose("case-log-armedforces-no-field", allow_label_click: true) + expect(page).not_to have_selector("#reservist_div") + choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true) + expect(page).to have_unchecked_field("case-log-reservist-no-field", visible: false) + end + end +end diff --git a/spec/features/form/form_navigation_spec.rb b/spec/features/form/form_navigation_spec.rb new file mode 100644 index 000000000..0c697ae3d --- /dev/null +++ b/spec/features/form/form_navigation_spec.rb @@ -0,0 +1,80 @@ +require "rails_helper" + +RSpec.describe "Form Navigation" do + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:id) { case_log.id } + let(:question_answers) do + { + tenant_code: { type: "text", answer: "BZ737", path: "tenant_code" }, + age1: { type: "numeric", answer: 25, path: "person_1_age" }, + sex1: { type: "radio", answer: "Female", path: "person_1_gender" }, + other_hhmemb: { type: "numeric", answer: 2, path: "household_number_of_other_members" }, + } + end + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + describe "Create new log" do + it "redirects to the task list for the new log" do + visit("/case_logs") + click_link("Create new log") + id = CaseLog.order(created_at: :desc).first.id + expect(page).to have_content("Tasklist for log #{id}") + end + end + + describe "Viewing a log" do + it "questions can be accessed by url" do + visit("/case_logs/#{id}/person_1_age") + expect(page).to have_field("case-log-age1-field") + end + + it "a question page leads to the next question defined in the form definition" do + pages = question_answers.map { |_key, val| val[:path] } + pages[0..-2].each_with_index do |val, index| + visit("/case_logs/#{id}/#{val}") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/#{pages[index + 1]}") + end + end + + describe "Back link directs correctly", js: true do + it "go back to tasklist page from tenant code" do + visit("/case_logs/#{id}") + visit("/case_logs/#{id}/tenant_code") + click_link(text: "Back") + expect(page).to have_content("Tasklist for log #{id}") + end + + it "go back to tenant code page from tenant age page", js: true do + visit("/case_logs/#{id}/tenant_code") + click_button("Save and continue") + visit("/case_logs/#{id}/person_1_age") + click_link(text: "Back") + expect(page).to have_field("case-log-tenant-code-field") + end + + it "doesn't get stuck in infinite loops", js: true do + visit("/case_logs") + visit("/case_logs/#{id}/net_income") + fill_in("case-log-earnings-field", with: 740) + choose("case-log-incfreq-weekly-field", allow_label_click: true) + click_button("Save and continue") + click_link(text: "Back") + click_link(text: "Back") + expect(page).to have_current_path("/case_logs") + end + + context "when changing an answer from the check answers page", js: true do + it "the back button routes correctly" do + visit("/case_logs/#{id}/household_characteristics/check_answers") + first("a", text: /Answer/).click + click_link("Back") + expect(page).to have_current_path("/case_logs/#{id}/household_characteristics/check_answers") + end + end + end + end +end diff --git a/spec/features/form/helpers.rb b/spec/features/form/helpers.rb new file mode 100644 index 000000000..e5ea8e173 --- /dev/null +++ b/spec/features/form/helpers.rb @@ -0,0 +1,18 @@ +module Helpers + def fill_in_number_question(case_log_id, question, value, path) + visit("/case_logs/#{case_log_id}/#{path}") + fill_in("case-log-#{question.to_s.dasherize}-field", with: value) + click_button("Save and continue") + end + + def answer_all_questions_in_income_subsection(case_log) + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-earnings-field", with: 18_000) + choose("case-log-incfreq-yearly-field") + click_button("Save and continue") + choose("case-log-benefits-all-field") + click_button("Save and continue") + choose("case-log-hb-prefer-not-to-say-field") + click_button("Save and continue") + end +end diff --git a/spec/features/form/page_routing_spec.rb b/spec/features/form/page_routing_spec.rb new file mode 100644 index 000000000..0a9146c61 --- /dev/null +++ b/spec/features/form/page_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe "Form Page Routing" do + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:id) { case_log.id } + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + allow_any_instance_of(CaseLogValidator).to receive(:validate_pregnancy).and_return(true) + end + + it "can route the user to a different page based on their answer on the current page", js: true do + visit("case_logs/#{id}/conditional_question") + # using a question name that is already in the db to avoid + # having to add a new column to the db for this test + choose("case-log-preg-occ-yes-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/conditional_question_yes_page") + click_link(text: "Back") + expect(page).to have_current_path("/case_logs/#{id}/conditional_question") + choose("case-log-preg-occ-no-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page") + end + + it "can route based on multiple conditions", js: true do + visit("/case_logs/#{id}/person_1_gender") + choose("case-log-sex1-female-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/household_number_of_other_members") + visit("/case_logs/#{id}/conditional_question") + choose("case-log-preg-occ-no-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/conditional_question/check_answers") + end +end diff --git a/spec/features/form/saving_data_spec.rb b/spec/features/form/saving_data_spec.rb new file mode 100644 index 000000000..9a3b0cd41 --- /dev/null +++ b/spec/features/form/saving_data_spec.rb @@ -0,0 +1,89 @@ +require "rails_helper" + +RSpec.describe "Form Saving Data" do + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:id) { case_log.id } + let(:case_log_with_checkbox_questions_answered) do + FactoryBot.create( + :case_log, :in_progress, + housingneeds_a: "Yes", + housingneeds_c: "Yes" + ) + end + let(:question_answers) do + { + tenant_code: { type: "text", answer: "BZ737", path: "tenant_code" }, + age1: { type: "numeric", answer: 25, path: "person_1_age" }, + sex1: { type: "radio", answer: "Female", path: "person_1_gender" }, + other_hhmemb: { type: "numeric", answer: 2, path: "household_number_of_other_members" }, + } + end + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + it "updates model attributes correctly for each question" do + question_answers.each do |question, hsh| + type = hsh[:type] + answer = hsh[:answer] + path = hsh[:path] + original_value = case_log.send(question) + visit("/case_logs/#{id}/#{path}") + case type + when "text" + fill_in("case-log-#{question.to_s.dasherize}-field", with: answer) + when "radio" + choose("case-log-#{question.to_s.dasherize}-#{answer.parameterize}-field") + else + fill_in("case-log-#{question.to_s.dasherize}-field", with: answer) + end + expect { click_button("Save and continue") }.to change { + case_log.reload.send(question.to_s) + }.from(original_value).to(answer) + end + end + + it "updates total value of the rent", js: true do + visit("/case_logs/#{id}/rent") + + fill_in("case-log-brent-field", with: 3) + expect(page).to have_field("case-log-tcharge-field", with: "3") + + fill_in("case-log-scharge-field", with: 2) + expect(page).to have_field("case-log-tcharge-field", with: "5") + + fill_in("case-log-pscharge-field", with: 1) + expect(page).to have_field("case-log-tcharge-field", with: "6") + + fill_in("case-log-supcharg-field", with: 4) + expect(page).to have_field("case-log-tcharge-field", with: "10") + end + + it "displays number answers in inputs if they are already saved" do + visit("/case_logs/#{id}/property_postcode") + expect(page).to have_field("case-log-property-postcode-field", with: "P0 5ST") + end + + it "displays text answers in inputs if they are already saved" do + visit("/case_logs/#{id}/person_1_age") + expect(page).to have_field("case-log-age1-field", with: "17") + end + + it "displays checkbox answers in inputs if they are already saved" do + visit("/case_logs/#{case_log_with_checkbox_questions_answered.id}/accessibility_requirements") + # Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we pass false here + expect(page).to have_checked_field( + "case-log-accessibility-requirements-housingneeds-a-field", + visible: false, + ) + expect(page).to have_unchecked_field( + "case-log-accessibility-requirements-housingneeds-b-field", + visible: false, + ) + expect(page).to have_checked_field( + "case-log-accessibility-requirements-housingneeds-c-field", + visible: false, + ) + end +end diff --git a/spec/features/form/tasklist_page_spec.rb b/spec/features/form/tasklist_page_spec.rb new file mode 100644 index 000000000..6248e5838 --- /dev/null +++ b/spec/features/form/tasklist_page_spec.rb @@ -0,0 +1,53 @@ +require "rails_helper" +require_relative "helpers" + +RSpec.describe "Task List" do + include Helpers + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:empty_case_log) { FactoryBot.create(:case_log) } + let(:id) { case_log.id } + let(:status) { case_log.status } + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + it "displays a tasklist header" do + visit("/case_logs/#{id}") + expect(page).to have_content("Tasklist for log #{id}") + expect(page).to have_content("This submission is #{status.humanize.downcase}") + end + + it "displays a section status" do + visit("/case_logs/#{empty_case_log.id}") + + assert_selector ".govuk-tag", text: /Not started/, count: 8 + assert_selector ".govuk-tag", text: /Completed/, count: 0 + assert_selector ".govuk-tag", text: /Cannot start yet/, count: 1 + end + + it "shows the correct status if one section is completed" do + answer_all_questions_in_income_subsection(empty_case_log) + visit("/case_logs/#{empty_case_log.id}") + + assert_selector ".govuk-tag", text: /Not started/, count: 7 + assert_selector ".govuk-tag", text: /Completed/, count: 1 + assert_selector ".govuk-tag", text: /Cannot start yet/, count: 1 + end + + it "skips to the first section if no answers are completed" do + visit("/case_logs/#{empty_case_log.id}") + expect(page).to have_link("Skip to next incomplete section", href: /#household_characteristics/) + end + + it "shows the number of completed sections if no sections are completed" do + visit("/case_logs/#{empty_case_log.id}") + expect(page).to have_content("You've completed 0 of 9 sections.") + end + + it "shows the number of completed sections if one section is completed" do + answer_all_questions_in_income_subsection(empty_case_log) + visit("/case_logs/#{empty_case_log.id}") + expect(page).to have_content("You've completed 1 of 9 sections.") + end +end diff --git a/spec/features/form/validations_spec.rb b/spec/features/form/validations_spec.rb new file mode 100644 index 000000000..43ebcf732 --- /dev/null +++ b/spec/features/form/validations_spec.rb @@ -0,0 +1,139 @@ +require "rails_helper" +require_relative "helpers" + +RSpec.describe "validations" do + include Helpers + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:empty_case_log) { FactoryBot.create(:case_log) } + let(:id) { case_log.id } + + before do + allow_any_instance_of(CaseLogsController).to receive(:authenticate_user!).and_return(true) + end + + describe "Question validation" do + context "given an invalid tenant age" do + it " of less than 0 it shows validation" do + visit("/case_logs/#{id}/person_1_age") + fill_in_number_question(empty_case_log.id, "age1", -5, "person_1_age") + expect(page).to have_selector("#error-summary-title") + expect(page).to have_selector("#case-log-age1-error") + expect(page).to have_selector("#case-log-age1-field-error") + end + + it " of greater than 120 it shows validation" do + visit("/case_logs/#{id}/person_1_age") + fill_in_number_question(empty_case_log.id, "age1", 121, "person_1_age") + expect(page).to have_selector("#error-summary-title") + expect(page).to have_selector("#case-log-age1-error") + expect(page).to have_selector("#case-log-age1-field-error") + end + end + end + + describe "date validation", js: true do + def fill_in_date(case_log_id, question, day, month, year, path) + visit("/case_logs/#{case_log_id}/#{path}") + fill_in("#{question}_1i", with: year) + fill_in("#{question}_2i", with: month) + fill_in("#{question}_3i", with: day) + end + + it "does not allow out of range dates to be submitted" do + fill_in_date(id, "case_log_mrcdate", 3100, 12, 2000, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + + fill_in_date(id, "case_log_mrcdate", 12, 1, 20_000, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + + fill_in_date(id, "case_log_mrcdate", 13, 100, 2020, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + + fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers") + end + + it "does not allow non numeric inputs to be submitted" do + fill_in_date(id, "case_log_mrcdate", "abc", "de", "ff", "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + end + + it "does not allow partial inputs to be submitted" do + fill_in_date(id, "case_log_mrcdate", 21, 12, nil, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + + fill_in_date(id, "case_log_mrcdate", 12, nil, 2000, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + + fill_in_date(id, "case_log_mrcdate", nil, 10, 2020, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs") + end + + it "allows valid inputs to be submitted" do + fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs") + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers") + end + end + + describe "Soft Validation" do + context "given a weekly net income that is above the expected amount for the given economic status but below the hard max" do + let(:case_log) { FactoryBot.create(:case_log, :in_progress, ecstat1: "Full-time - 30 hours or more") } + let(:income_over_soft_limit) { 750 } + let(:income_under_soft_limit) { 700 } + + it "prompts the user to confirm the value is correct", js: true do + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-earnings-field", with: income_over_soft_limit) + choose("case-log-incfreq-weekly-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_content("Are you sure this is correct?") + check("case-log-override-net-income-validation-override-net-income-validation-field", allow_label_click: true) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{case_log.id}/net_income_uc_proportion") + end + + it "does not require confirming the value if the value is amended" do + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-earnings-field", with: income_over_soft_limit) + choose("case-log-incfreq-weekly-field", allow_label_click: true) + click_button("Save and continue") + fill_in("case-log-earnings-field", with: income_under_soft_limit) + click_button("Save and continue") + expect(page).to have_current_path("/case_logs/#{case_log.id}/net_income_uc_proportion") + case_log.reload + expect(case_log.override_net_income_validation).to be_nil + end + + it "clears the confirmation question if the amount was amended and the page is returned to using the back button", js: true do + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-earnings-field", with: income_over_soft_limit) + choose("case-log-incfreq-weekly-field", allow_label_click: true) + click_button("Save and continue") + fill_in("case-log-earnings-field", with: income_under_soft_limit) + click_button("Save and continue") + click_link(text: "Back") + expect(page).to have_no_content("Are you sure this is correct?") + end + + it "does not clear the confirmation question if the page is returned to using the back button and the amount is still over the soft limit", js: true do + visit("/case_logs/#{case_log.id}/net_income") + fill_in("case-log-earnings-field", with: income_over_soft_limit) + choose("case-log-incfreq-weekly-field", allow_label_click: true) + click_button("Save and continue") + check("case-log-override-net-income-validation-override-net-income-validation-field", allow_label_click: true) + click_button("Save and continue") + click_link(text: "Back") + expect(page).to have_content("Are you sure this is correct?") + end + end + end +end diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 0f9c82050..48adb0f6c 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "User Features" do it " is redirected to case logs after signing in" do visit("/case_logs") - fill_in("user_email", with: "test@example.com") + fill_in("user_email", with: user.email) fill_in("user_password", with: "pAssword1") click_button("Sign in") expect(page).to have_current_path("/case_logs") @@ -25,16 +25,16 @@ RSpec.describe "User Features" do it " is redirected to check your email page after submitting an email on the reset password page" do visit("/users/password/new") - fill_in("user_email", with: "test@example.com") + fill_in("user_email", with: user.email) click_button("Send email") expect(page).to have_content("Check your email") end it " is shown their email on the password reset confirmation page" do visit("/users/password/new") - fill_in("user_email", with: "test@example.com") + fill_in("user_email", with: user.email) click_button("Send email") - expect(page).to have_content("test@example.com") + expect(page).to have_content(user.email) end it " is shown the reset password confirmation page even if their email doesn't exist in the system" do @@ -46,7 +46,7 @@ RSpec.describe "User Features" do it " is sent a reset password email" do visit("/users/password/new") - fill_in("user_email", with: "test@example.com") + fill_in("user_email", with: user.email) expect { click_button("Send email") }.to change { ActionMailer::Base.deliveries.count }.by(1) end end diff --git a/spec/helpers/tasklist_helper_spec.rb b/spec/helpers/tasklist_helper_spec.rb index 11552f014..8a0802b85 100644 --- a/spec/helpers/tasklist_helper_spec.rb +++ b/spec/helpers/tasklist_helper_spec.rb @@ -1,8 +1,8 @@ require "rails_helper" RSpec.describe TasklistHelper do - let(:empty_case_log) { FactoryBot.build(:case_log) } - let(:case_log) { FactoryBot.build(:case_log, :in_progress) } + let(:empty_case_log) { FactoryBot.create(:case_log) } + let(:case_log) { FactoryBot.create(:case_log, :in_progress) } form_handler = FormHandler.instance let(:form) { form_handler.get_form("test_form") } diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index e19ce4eec..5540e547e 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -4,14 +4,17 @@ RSpec.describe Form, type: :model do describe "#new" do it "validates age is a number" do expect { CaseLog.create!(age1: "random") }.to raise_error(ActiveRecord::RecordInvalid) + expect { CaseLog.create!(age3: "random") }.to raise_error(ActiveRecord::RecordInvalid) end it "validates age is under 120" do expect { CaseLog.create!(age1: 121) }.to raise_error(ActiveRecord::RecordInvalid) + expect { CaseLog.create!(age3: 121) }.to raise_error(ActiveRecord::RecordInvalid) end it "validates age is over 0" do expect { CaseLog.create!(age1: 0) }.to raise_error(ActiveRecord::RecordInvalid) + expect { CaseLog.create!(age3: 0) }.to raise_error(ActiveRecord::RecordInvalid) end it "validates number of relets is a number" do @@ -54,6 +57,15 @@ RSpec.describe Form, type: :model do CaseLog.create!(reasonpref: "No", rp_homeless: "No") }.not_to raise_error end + + it "validates that no reason has been provided" do + expect { + CaseLog.create!( + reasonpref: "No", + rp_medwel: "Yes" + ) + }.to raise_error(ActiveRecord::RecordInvalid) + end end context "reason for leaving last settled home validation" do @@ -424,6 +436,14 @@ RSpec.describe Form, type: :model do end end + describe "incref" do + let(:case_log) { FactoryBot.build(:case_log, net_income_known: "Prefer not to say") } + + it "sets income refused to Yes" do + expect(case_log.incref).to eq(1) + end + end + describe "weekly_net_income" do let(:net_income) { 5000 } let(:case_log) { FactoryBot.build(:case_log, earnings: net_income) } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index e948ebd4c..111039944 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -6,10 +6,19 @@ require File.expand_path("../config/environment", __dir__) abort("The Rails environment is running in production mode!") if Rails.env.production? require "rspec/rails" require "capybara/rspec" -require "database_cleaner/active_record" +require "selenium-webdriver" -# Comment to run `js: true specs` with visible browser interaction -Capybara.javascript_driver = :selenium_headless +Capybara.register_driver :headless do |app| + options = Selenium::WebDriver::Firefox::Options.new + options.add_argument("--headless") + + Capybara::Selenium::Driver.new(app, + browser: :firefox, + capabilities: options + ) +end + +Capybara.javascript_driver = :headless # Add additional requires below this line. Rails is not loaded until this point! @@ -43,49 +52,7 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - # config.use_transactional_fixtures = true - - config.before(:suite) do - if config.use_transactional_fixtures? - raise(<<-MSG) - Delete line `config.use_transactional_fixtures = true` from rails_helper.rb - (or set it to false) to prevent uncommitted transactions being used in - JavaScript-dependent specs. - - During testing, the app-under-test that the browser driver connects to - uses a different database connection to the database connection used by - the spec. The app's database connection would not be able to access - uncommitted transaction data setup over the spec's database connection. - MSG - end - - DatabaseCleaner.clean_with(:truncation) - end - - config.before(:each) do - DatabaseCleaner.strategy = :transaction - end - - config.before(:each, type: :feature) do - # :rack_test driver's Rack app under test shares database connection - # with the specs, so continue to use transaction strategy for speed. - driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test - - unless driver_shares_db_connection_with_specs - # Driver is probably for an external browser with an app - # under test that does *not* share a database connection with the - # specs, so use truncation strategy. - DatabaseCleaner.strategy = :truncation - end - end - - config.before(:each) do - DatabaseCleaner.start - end - - config.append_after(:each) do - DatabaseCleaner.clean - end + config.use_transactional_fixtures = true # You can uncomment this line to turn off ActiveRecord support entirely. # config.use_active_record = false diff --git a/spec/requests/case_log_controller_spec.rb b/spec/requests/case_log_controller_spec.rb index 55a1c9821..c91bb2938 100644 --- a/spec/requests/case_log_controller_spec.rb +++ b/spec/requests/case_log_controller_spec.rb @@ -231,34 +231,47 @@ RSpec.describe CaseLogsController, type: :request do end let(:id) { case_log.id } - before do - delete "/case_logs/#{id}", headers: headers - end + context "expected deletion" do + before do + delete "/case_logs/#{id}", headers: headers + end - it "returns http success" do - expect(response).to have_http_status(:success) - end + it "returns http success" do + expect(response).to have_http_status(:success) + end - it "soft deletes the case log" do - expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound) - expect(CaseLog.with_discarded.find(id)).to be_a(CaseLog) - end + it "soft deletes the case log" do + expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound) + expect(CaseLog.with_discarded.find(id)).to be_a(CaseLog) + end - context "invalid case log id" do - let(:id) { (CaseLog.order(:id).last&.id || 0) + 1 } + context "invalid case log id" do + let(:id) { (CaseLog.order(:id).last&.id || 0) + 1 } - it "returns 404" do - expect(response).to have_http_status(:not_found) + it "returns 404" do + expect(response).to have_http_status(:not_found) + end + end + + context "request with invalid credentials" do + let(:basic_credentials) do + ActionController::HttpAuthentication::Basic.encode_credentials(api_username, "Oops") + end + + it "returns 401" do + expect(response).to have_http_status(:unauthorized) + end end end - context "request with invalid credentials" do - let(:basic_credentials) do - ActionController::HttpAuthentication::Basic.encode_credentials(api_username, "Oops") + context "deletion fails" do + before do + allow_any_instance_of(CaseLog).to receive(:discard).and_return(false) + delete "/case_logs/#{id}", headers: headers end - it "returns 401" do - expect(response).to have_http_status(:unauthorized) + it "returns an unprocessable entity 422" do + expect(response).to have_http_status(:unprocessable_entity) end end end diff --git a/spec/views/case_log_index_view_spec.rb b/spec/views/case_log_index_view_spec.rb index cc826e3c8..41ea2f75d 100644 --- a/spec/views/case_log_index_view_spec.rb +++ b/spec/views/case_log_index_view_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe "case_logs/index" do - let(:in_progress_log) { FactoryBot.build(:case_log, :in_progress) } - let(:completed_log) { FactoryBot.build(:case_log, :completed) } + let(:in_progress_log) { FactoryBot.create(:case_log, :in_progress) } + let(:completed_log) { FactoryBot.create(:case_log, :completed) } context "given an in progress log list" do it "renders a table for in progress logs only" do