40 changed files with 752 additions and 580 deletions
			
			
		| @ -1,57 +1,20 @@ | |||||||
| module CheckAnswersHelper | module CheckAnswersHelper | ||||||
|   def total_answered_questions(subsection, case_log, form) |   def display_answered_questions_summary(subsection, case_log) | ||||||
|     total_questions(subsection, case_log, form).keys.count do |question_key| |     total = subsection.applicable_questions_count(case_log) | ||||||
|       case_log[question_key].present? |     answered = subsection.answered_questions_count(case_log) | ||||||
|     end |     if total == answered | ||||||
|   end |       '<p class="govuk-body govuk-!-margin-bottom-7">You answered all the questions</p>'.html_safe | ||||||
| 
 |     else | ||||||
|   def total_number_of_questions(subsection, case_log, form) |       "<p class=\"govuk-body govuk-!-margin-bottom-7\">You answered #{answered} of #{total} questions</p> | ||||||
|     total_questions(subsection, case_log, form).count |       #{create_next_missing_question_link(subsection, case_log)}".html_safe | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def total_questions(subsection, case_log, form) |  | ||||||
|     questions = form.questions_for_subsection(subsection) |  | ||||||
|     form.filter_conditional_questions(questions, case_log) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def get_next_page_name(form, page_name, case_log) |  | ||||||
|     page = form.all_pages[page_name] |  | ||||||
|     if page.key?("conditional_route_to") |  | ||||||
|       page["conditional_route_to"].each do |conditional_page_name, condition| |  | ||||||
|         unless condition.any? { |field, value| case_log[field].blank? || !value.include?(case_log[field]) } |  | ||||||
|           return conditional_page_name |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |     end | ||||||
|     form.next_page(page_name) |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def create_update_answer_link(question_title, question_info, case_log, form) | private | ||||||
|     page = form.page_for_question(question_title) |  | ||||||
|     link_name = if question_info["type"] == "checkbox" |  | ||||||
|                   question_info["answer_options"].keys.any? { |key| case_log[key] == "Yes" } ? "Change" : "Answer" |  | ||||||
|                 else |  | ||||||
|                   case_log[question_title].blank? ? "Answer" : "Change" |  | ||||||
|                 end |  | ||||||
|     link_to(link_name, "/case_logs/#{case_log.id}/#{page}", class: "govuk-link").html_safe |  | ||||||
|   end |  | ||||||
| 
 | 
 | ||||||
|   def create_next_missing_question_link(case_log_id, subsection, case_log, form) |   def create_next_missing_question_link(subsection, case_log) | ||||||
|     pages_to_fill_in = [] |     pages_to_fill_in = subsection.unanswered_questions(case_log).map(&:page) | ||||||
|     form.pages_for_subsection(subsection).each do |page_title, page_info| |     url = "/case_logs/#{case_log.id}/#{pages_to_fill_in.first.id}" | ||||||
|       page_info["questions"].any? { |question| case_log[question].blank? } |  | ||||||
|       pages_to_fill_in << page_title |  | ||||||
|     end |  | ||||||
|     url = "/case_logs/#{case_log_id}/#{pages_to_fill_in.first}" |  | ||||||
|     link_to("Answer the missing questions", url, class: "govuk-link").html_safe |     link_to("Answer the missing questions", url, class: "govuk-link").html_safe | ||||||
|   end |   end | ||||||
| 
 |  | ||||||
|   def display_answered_questions_summary(subsection, case_log, form) |  | ||||||
|     if total_answered_questions(subsection, case_log, form) == total_number_of_questions(subsection, case_log, form) |  | ||||||
|       '<p class="govuk-body govuk-!-margin-bottom-7">You answered all the questions</p>'.html_safe |  | ||||||
|     else |  | ||||||
|       "<p class=\"govuk-body govuk-!-margin-bottom-7\">You answered #{total_answered_questions(subsection, case_log, form)} of #{total_number_of_questions(subsection, case_log, form)} questions</p> |  | ||||||
|       #{create_next_missing_question_link(case_log['id'], subsection, case_log, form)}".html_safe |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|  | |||||||
| @ -1,11 +1,9 @@ | |||||||
| module ConditionalQuestionsHelper | module ConditionalQuestionsHelper | ||||||
|   def conditional_questions_for_page(page) |   def conditional_questions_for_page(page) | ||||||
|     page["questions"].values.map { |question| |     page.questions.map(&:conditional_for).compact.map(&:keys).flatten | ||||||
|       question["conditional_for"] |  | ||||||
|     }.compact.map(&:keys).flatten |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def display_question_key_div(page_info, question_key) |   def display_question_key_div(page, question) | ||||||
|     "style='display:none;'".html_safe if conditional_questions_for_page(page_info).include?(question_key) |     "style='display:none;'".html_safe if conditional_questions_for_page(page).include?(question.id) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | |||||||
| @ -0,0 +1,30 @@ | |||||||
|  | class Form::Page | ||||||
|  |   attr_accessor :id, :header, :description, :questions, :soft_validations, | ||||||
|  |                 :depends_on, :subsection | ||||||
|  | 
 | ||||||
|  |   def initialize(id, hsh, subsection) | ||||||
|  |     @id = id | ||||||
|  |     @header = hsh["header"] | ||||||
|  |     @description = hsh["description"] | ||||||
|  |     @questions = hsh["questions"].map { |q_id, q| Form::Question.new(q_id, q, self) } | ||||||
|  |     @depends_on = hsh["depends_on"] | ||||||
|  |     @soft_validations = hsh["soft_validations"]&.map { |v_id, s| Form::Question.new(v_id, s, self) } | ||||||
|  |     @subsection = subsection | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def expected_responses | ||||||
|  |     questions + (soft_validations || []) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def has_soft_validations? | ||||||
|  |     soft_validations.present? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def routed_to?(case_log) | ||||||
|  |     return true unless depends_on | ||||||
|  | 
 | ||||||
|  |     depends_on.all? do |question, value| | ||||||
|  |       case_log[question].present? && case_log[question] == value | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,80 @@ | |||||||
|  | class Form::Question | ||||||
|  |   attr_accessor :id, :header, :hint_text, :description, :questions, | ||||||
|  |                 :type, :min, :max, :step, :fields_to_add, :result_field, | ||||||
|  |                 :conditional_for, :readonly, :answer_options, :page, :check_answer_label | ||||||
|  | 
 | ||||||
|  |   def initialize(id, hsh, page) | ||||||
|  |     @id = id | ||||||
|  |     @check_answer_label = hsh["check_answer_label"] | ||||||
|  |     @header = hsh["header"] | ||||||
|  |     @hint_text = hsh["hint_text"] | ||||||
|  |     @type = hsh["type"] | ||||||
|  |     @min = hsh["min"] | ||||||
|  |     @max = hsh["max"] | ||||||
|  |     @step = hsh["step"] | ||||||
|  |     @fields_to_add = hsh["fields-to-add"] | ||||||
|  |     @result_field = hsh["result-field"] | ||||||
|  |     @readonly = hsh["readonly"] | ||||||
|  |     @answer_options = hsh["answer_options"] | ||||||
|  |     @conditional_for = hsh["conditional_for"] | ||||||
|  |     @page = page | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   delegate :subsection, to: :page | ||||||
|  |   delegate :form, to: :subsection | ||||||
|  | 
 | ||||||
|  |   def answer_label(case_log) | ||||||
|  |     return checkbox_answer_label(case_log) if type == "checkbox" | ||||||
|  | 
 | ||||||
|  |     case_log[id].to_s | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def read_only? | ||||||
|  |     !!readonly | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def conditional_on | ||||||
|  |     @conditional_on ||= form.conditional_question_conditions.select do |condition| | ||||||
|  |       condition[:to] == id | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def enabled?(case_log) | ||||||
|  |     return true if conditional_on.blank? | ||||||
|  | 
 | ||||||
|  |     conditional_on.map { |condition| evaluate_condition(condition, case_log) }.all? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def update_answer_link_name(case_log) | ||||||
|  |     if type == "checkbox" | ||||||
|  |       answer_options.keys.any? { |key| case_log[key] == "Yes" } ? "Change" : "Answer" | ||||||
|  |     else | ||||||
|  |       case_log[id].blank? ? "Answer" : "Change" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  | private | ||||||
|  | 
 | ||||||
|  |   def checkbox_answer_label(case_log) | ||||||
|  |     answer = [] | ||||||
|  |     answer_options.each { |key, value| case_log[key] == "Yes" ? answer << value : nil } | ||||||
|  |     answer.join(", ") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def evaluate_condition(condition, case_log) | ||||||
|  |     case page.questions.find { |q| q.id == condition[:from] }.type | ||||||
|  |     when "numeric" | ||||||
|  |       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" | ||||||
|  |       case_log[condition[:from]].present? && condition[:cond].include?(case_log[condition[:from]]) | ||||||
|  |     else | ||||||
|  |       raise "Not implemented yet" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | class Form::Section | ||||||
|  |   attr_accessor :id, :label, :subsections, :form | ||||||
|  | 
 | ||||||
|  |   def initialize(id, hsh, form) | ||||||
|  |     @id = id | ||||||
|  |     @label = hsh["label"] | ||||||
|  |     @form = form | ||||||
|  |     @subsections = hsh["subsections"].map { |s_id, s| Form::Subsection.new(s_id, s, self) } | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | class Form::Subsection | ||||||
|  |   attr_accessor :id, :label, :section, :pages, :depends_on, :form | ||||||
|  | 
 | ||||||
|  |   def initialize(id, hsh, section) | ||||||
|  |     @id = id | ||||||
|  |     @label = hsh["label"] | ||||||
|  |     @depends_on = hsh["depends_on"] | ||||||
|  |     @pages = hsh["pages"].map { |s_id, p| Form::Page.new(s_id, p, self) } | ||||||
|  |     @section = section | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   delegate :form, to: :section | ||||||
|  | 
 | ||||||
|  |   def questions | ||||||
|  |     @questions ||= pages.flat_map(&:questions) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def enabled?(case_log) | ||||||
|  |     return true unless depends_on | ||||||
|  | 
 | ||||||
|  |     depends_on.all? do |subsection_id, dependent_status| | ||||||
|  |       form.get_subsection(subsection_id).status(case_log) == dependent_status.to_sym | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def status(case_log) | ||||||
|  |     unless enabled?(case_log) | ||||||
|  |       return :cannot_start_yet | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     qs = applicable_questions(case_log) | ||||||
|  |     return :not_started if qs.all? { |question| case_log[question.id].blank? } | ||||||
|  |     return :completed if qs.all? { |question| case_log[question.id].present? } | ||||||
|  | 
 | ||||||
|  |     :in_progress | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def is_incomplete?(case_log) | ||||||
|  |     %i[not_started in_progress].include?(status(case_log)) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def is_started?(case_log) | ||||||
|  |     %i[in_progress completed].include?(status(case_log)) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def applicable_questions_count(case_log) | ||||||
|  |     applicable_questions(case_log).count | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def answered_questions_count(case_log) | ||||||
|  |     answered_questions(case_log).count | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def applicable_questions(case_log) | ||||||
|  |     questions.select { |q| q.page.routed_to?(case_log) && q.enabled?(case_log) } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def answered_questions(case_log) | ||||||
|  |     applicable_questions(case_log).select { |question| case_log[question.id].present? } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def unanswered_questions(case_log) | ||||||
|  |     applicable_questions(case_log) - answered_questions(case_log) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -1,11 +1,11 @@ | |||||||
| <div class="govuk-summary-list__row"> | <div class="govuk-summary-list__row"> | ||||||
|   <dt class="govuk-summary-list__key"> |   <dt class="govuk-summary-list__key"> | ||||||
|     <%= question_info["check_answer_label"].to_s.present? ?  question_info["check_answer_label"].to_s : question_info["header"].to_s%> |     <%= question.check_answer_label.to_s.present? ?  question.check_answer_label.to_s : question.header.to_s %> | ||||||
|   <dt> |   <dt> | ||||||
|   <dd class="govuk-summary-list__value"> |   <dd class="govuk-summary-list__value"> | ||||||
|     <%= form.get_answer_label(@case_log, question_title) %> |     <%= question.answer_label(@case_log) %> | ||||||
|   </dd> |   </dd> | ||||||
|   <dd class="govuk-summary-list__actions"> |   <dd class="govuk-summary-list__actions"> | ||||||
|     <%= create_update_answer_link(question_title, question_info, @case_log, form) %> |     <%= link_to(question.update_answer_link_name(@case_log), "/case_logs/#{@case_log.id}/#{question.page.id}", class: "govuk-link").html_safe %> | ||||||
|   </dd> |   </dd> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| <%= f.govuk_date_field question_key, | <%= f.govuk_date_field question.id.to_sym, | ||||||
|     hint: { text: question["hint_text"] }, |     hint: { text: question.hint_text }, | ||||||
|     legend: { text: question["header"].html_safe, size: "l"}, |     legend: { text: question.header.html_safe, size: "l"}, | ||||||
|     width: 20, |     width: 20, | ||||||
|     **stimulus_html_attributes(question) |     **stimulus_html_attributes(question) | ||||||
| %> | %> | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| <%= f.govuk_number_field question_key, | <%= f.govuk_number_field question.id.to_sym, | ||||||
|     hint: { text: question["hint_text"] }, |     hint: { text: question.hint_text }, | ||||||
|     label: { text: question["header"].html_safe, size: "l"}, |     label: { text: question.header.html_safe, size: "l"}, | ||||||
|     min: question["min"], max: question["max"], step: question["step"], |     min: question.min, max: question.max, step: question.step, | ||||||
|     width: 20, :readonly => question["readonly"], |     width: 20, :readonly => question.read_only?, | ||||||
|    **stimulus_html_attributes(question) |    **stimulus_html_attributes(question) | ||||||
| %> | %> | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| <%= f.govuk_radio_buttons_fieldset question_key, | <%= f.govuk_radio_buttons_fieldset question.id.to_sym, | ||||||
|     legend: { text: question["header"].html_safe, size: "l" }, |     legend: { text: question.header.html_safe, size: "l" }, | ||||||
|     hint: { text: question["hint_text"] }, |     hint: { text: question.hint_text }, | ||||||
|     small: (question["answer_options"].size > 5) do %> |     small: (question.answer_options.size > 5) do %> | ||||||
| 
 | 
 | ||||||
|     <% question["answer_options"].map do |key, val| %> |     <% question.answer_options.map do |key, val| %> | ||||||
|       <% if key.starts_with?("divider") %> |       <% if key.starts_with?("divider") %> | ||||||
|         <%= f.govuk_radio_divider %> |         <%= f.govuk_radio_divider %> | ||||||
|       <% else %> |       <% else %> | ||||||
|         <%= f.govuk_radio_button question_key, val, label: { text: val }, **stimulus_html_attributes(question) %> |         <%= f.govuk_radio_button question.id, val, label: { text: val }, **stimulus_html_attributes(question) %> | ||||||
|       <% end %> |       <% end %> | ||||||
|     <% end %> |     <% end %> | ||||||
| <% end %> | <% end %> | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| <%= f.govuk_text_field question_key, | <%= f.govuk_text_field question.id.to_sym, | ||||||
|     hint: { text: question["hint_text"] }, |     hint: { text: question.hint_text }, | ||||||
|     label: { text: question["header"].html_safe, size: "l"}, |     label: { text: question.header.html_safe, size: "l"}, | ||||||
|     width: 20, |     width: 20, | ||||||
|     **stimulus_html_attributes(question) |     **stimulus_html_attributes(question) | ||||||
| %> | %> | ||||||
|  | |||||||
| @ -0,0 +1,10 @@ | |||||||
|  | ### ADR - 011: Splitting the form parsing into objects | ||||||
|  | 
 | ||||||
|  | Initially a single "Form" class handled the parsing of the form definition JSON as well as a lot of the logic around what different sections meant. This works fine but led to a lot of places in code where we're passing around arguments to determine whether a page or section should or shouldn't do something rather than being able to ask it directly. Refactoring this into smaller form domain object classes has several benefits: | ||||||
|  | 
 | ||||||
|  | - It's easier to compare the form definition JSON to the code classes and reason about what fields can be passed and what effect they'll have | ||||||
|  | - It moves business logic out of the helpers and keeps them to just dealing with display logic | ||||||
|  | - It makes it easier to unit test form functionality, and group that into smaller chunks | ||||||
|  | - It allows for less passing of arguments. e.g. `page.routed_to?(case_log)` vs `form.was_page_routed_to?(page, case_log)` | ||||||
|  | 
 | ||||||
|  | This abstraction is likely still not the best (the form vs case log split) but this seems like an improvement that can be iterated on.  | ||||||
| @ -0,0 +1,66 @@ | |||||||
|  | require "rails_helper" | ||||||
|  | 
 | ||||||
|  | RSpec.describe Form::Page, type: :model do | ||||||
|  |   let(:form) { FormHandler.instance.get_form("test_form") } | ||||||
|  |   let(:section_id) { "rent_and_charges" } | ||||||
|  |   let(:section_definition) { form.form_definition["sections"][section_id] } | ||||||
|  |   let(:section) { Form::Section.new(section_id, section_definition, form) } | ||||||
|  |   let(:subsection_id) { "income_and_benefits" } | ||||||
|  |   let(:subsection_definition) { section_definition["subsections"][subsection_id] } | ||||||
|  |   let(:subsection) { Form::Subsection.new(subsection_id, subsection_definition, section) } | ||||||
|  |   let(:page_id) { "net_income" } | ||||||
|  |   let(:page_definition) { subsection_definition["pages"][page_id] } | ||||||
|  |   subject { Form::Page.new(page_id, page_definition, subsection) } | ||||||
|  | 
 | ||||||
|  |   it "has an id" do | ||||||
|  |     expect(subject.id).to eq(page_id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a header" do | ||||||
|  |     expect(subject.header).to eq("Test header") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a description" do | ||||||
|  |     expect(subject.description).to eq("Some extra text for the page") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has questions" do | ||||||
|  |     expected_questions = %w[earnings incfreq] | ||||||
|  |     expect(subject.questions.map(&:id)).to eq(expected_questions) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has soft validations" do | ||||||
|  |     expected_soft_validations = %w[override_net_income_validation] | ||||||
|  |     expect(subject.soft_validations.map(&:id)).to eq(expected_soft_validations) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a soft_validation helper" do | ||||||
|  |     expect(subject.has_soft_validations?).to be true | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has expected form responses" do | ||||||
|  |     expected_responses = %w[earnings incfreq override_net_income_validation] | ||||||
|  |     expect(subject.expected_responses.map(&:id)).to eq(expected_responses) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "for a given case log" do | ||||||
|  |     let(:case_log) { FactoryBot.build(:case_log, :in_progress) } | ||||||
|  | 
 | ||||||
|  |     it "knows if it's been routed to" do | ||||||
|  |       expect(subject.routed_to?(case_log)).to be true | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context "given routing conditions" do | ||||||
|  |       let(:page_id) { "dependent_page" } | ||||||
|  | 
 | ||||||
|  |       it "evaluates not met conditions correctly" do | ||||||
|  |         expect(subject.routed_to?(case_log)).to be false | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it "evaluates not conditions correctly" do | ||||||
|  |         case_log.incfreq = "Weekly" | ||||||
|  |         expect(subject.routed_to?(case_log)).to be true | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,140 @@ | |||||||
|  | require "rails_helper" | ||||||
|  | 
 | ||||||
|  | RSpec.describe Form::Question, type: :model do | ||||||
|  |   let(:form) { FormHandler.instance.get_form("test_form") } | ||||||
|  |   let(:section_id) { "rent_and_charges" } | ||||||
|  |   let(:section_definition) { form.form_definition["sections"][section_id] } | ||||||
|  |   let(:section) { Form::Section.new(section_id, section_definition, form) } | ||||||
|  |   let(:subsection_id) { "income_and_benefits" } | ||||||
|  |   let(:subsection_definition) { section_definition["subsections"][subsection_id] } | ||||||
|  |   let(:subsection) { Form::Subsection.new(subsection_id, subsection_definition, section) } | ||||||
|  |   let(:page_id) { "net_income" } | ||||||
|  |   let(:page_definition) { subsection_definition["pages"][page_id] } | ||||||
|  |   let(:page) { Form::Page.new(page_id, page_definition, subsection) } | ||||||
|  |   let(:question_id) { "earnings" } | ||||||
|  |   let(:question_definition) { page_definition["questions"][question_id] } | ||||||
|  |   subject { Form::Question.new(question_id, question_definition, page) } | ||||||
|  | 
 | ||||||
|  |   it "has an id" do | ||||||
|  |     expect(subject.id).to eq(question_id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a header" do | ||||||
|  |     expect(subject.header).to eq("What is the tenant’s /and partner’s combined income after tax?") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a check answers label" do | ||||||
|  |     expect(subject.check_answer_label).to eq("Income") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a question type" do | ||||||
|  |     expect(subject.type).to eq("numeric") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "belongs to a page" do | ||||||
|  |     expect(subject.page).to eq(page) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "belongs to a subsection" do | ||||||
|  |     expect(subject.subsection).to eq(subsection) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a read only helper" do | ||||||
|  |     expect(subject.read_only?).to be false | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "when type is numeric" do | ||||||
|  |     it "has a min value" do | ||||||
|  |       expect(subject.min).to eq(0) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has a step value" do | ||||||
|  |       expect(subject.step).to eq(1) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "when type is radio" do | ||||||
|  |     let(:question_id) { "incfreq" } | ||||||
|  | 
 | ||||||
|  |     it "has answer options" do | ||||||
|  |       expected_answer_options = { "0" => "Weekly", "1" => "Monthly", "2" => "Yearly" } | ||||||
|  |       expect(subject.answer_options).to eq(expected_answer_options) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "when type is checkbox" do | ||||||
|  |     let(:page_id) { "dependent_page" } | ||||||
|  |     let(:question_id) { "dependent_question" } | ||||||
|  | 
 | ||||||
|  |     it "has answer options" do | ||||||
|  |       expected_answer_options = { "0" => "Option A", "1" => "Option B" } | ||||||
|  |       expect(subject.answer_options).to eq(expected_answer_options) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "when the question is read only" do | ||||||
|  |     let(:subsection_id) { "rent" } | ||||||
|  |     let(:page_id) { "rent" } | ||||||
|  |     let(:question_id) { "tcharge" } | ||||||
|  | 
 | ||||||
|  |     it "has a read only helper" do | ||||||
|  |       expect(subject.read_only?).to be true | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context "when the answer is part of a sum" do | ||||||
|  |       let(:question_id) { "pscharge" } | ||||||
|  | 
 | ||||||
|  |       it "has a result_field" do | ||||||
|  |         expect(subject.result_field).to eq("tcharge") | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it "has fields to sum" do | ||||||
|  |         expected_fields_to_sum = %w[brent scharge pscharge supcharg] | ||||||
|  |         expect(subject.fields_to_add).to eq(expected_fields_to_sum) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "for a given case log" do | ||||||
|  |     let(:case_log) { FactoryBot.build(:case_log, :in_progress) } | ||||||
|  | 
 | ||||||
|  |     it "has an answer label" do | ||||||
|  |       case_log.earnings = 100 | ||||||
|  |       expect(subject.answer_label(case_log)).to eq("100") | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has an update answer link text helper" do | ||||||
|  |       expect(subject.update_answer_link_name(case_log)).to eq("Answer") | ||||||
|  |       case_log[question_id] = 5 | ||||||
|  |       expect(subject.update_answer_link_name(case_log)).to eq("Change") | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context "when type is checkbox" do | ||||||
|  |       let(:section_id) { "household" } | ||||||
|  |       let(:subsection_id) { "household_needs" } | ||||||
|  |       let(:page_id) { "accessibility_requirements" } | ||||||
|  |       let(:question_id) { "accessibility_requirements" } | ||||||
|  | 
 | ||||||
|  |       it "has a joined answers label" do | ||||||
|  |         case_log.housingneeds_a = 1 | ||||||
|  |         case_log.housingneeds_c = 1 | ||||||
|  |         expected_answer_label = "Fully wheelchair accessible housing, Level access housing" | ||||||
|  |         expect(subject.answer_label(case_log)).to eq(expected_answer_label) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context "when a condition is present" do | ||||||
|  |       let(:page_id) { "housing_benefit" } | ||||||
|  |       let(:question_id) { "conditional_question" } | ||||||
|  | 
 | ||||||
|  |       it "knows whether it is enabled or not for unmet conditions" do | ||||||
|  |         expect(subject.enabled?(case_log)).to be false | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it "knows whether it is enabled or not for met conditions" do | ||||||
|  |         case_log.hb = "Housing Benefit, but not Universal Credit" | ||||||
|  |         expect(subject.enabled?(case_log)).to be true | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,21 @@ | |||||||
|  | require "rails_helper" | ||||||
|  | 
 | ||||||
|  | RSpec.describe Form::Section, type: :model do | ||||||
|  |   let(:form) { FormHandler.instance.get_form("test_form") } | ||||||
|  |   let(:section_id) { "household" } | ||||||
|  |   let(:section_definition) { form.form_definition["sections"][section_id] } | ||||||
|  |   subject { Form::Section.new(section_id, section_definition, form) } | ||||||
|  | 
 | ||||||
|  |   it "has an id" do | ||||||
|  |     expect(subject.id).to eq(section_id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a label" do | ||||||
|  |     expect(subject.label).to eq("About the household") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has subsections" do | ||||||
|  |     expected_subsections = %w[household_characteristics household_needs] | ||||||
|  |     expect(subject.subsections.map(&:id)).to eq(expected_subsections) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -0,0 +1,72 @@ | |||||||
|  | require "rails_helper" | ||||||
|  | 
 | ||||||
|  | RSpec.describe Form::Subsection, type: :model do | ||||||
|  |   let(:form) { FormHandler.instance.get_form("test_form") } | ||||||
|  |   let(:section_id) { "household" } | ||||||
|  |   let(:section_definition) { form.form_definition["sections"][section_id] } | ||||||
|  |   let(:section) { Form::Section.new(section_id, section_definition, form) } | ||||||
|  |   let(:subsection_id) { "household_characteristics" } | ||||||
|  |   let(:subsection_definition) { section_definition["subsections"][subsection_id] } | ||||||
|  |   subject { Form::Subsection.new(subsection_id, subsection_definition, section) } | ||||||
|  | 
 | ||||||
|  |   it "has an id" do | ||||||
|  |     expect(subject.id).to eq(subsection_id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has a label" do | ||||||
|  |     expect(subject.label).to eq("Household characteristics") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has pages" do | ||||||
|  |     expected_pages = %w[tenant_code person_1_age person_1_gender household_number_of_other_members] | ||||||
|  |     expect(subject.pages.map(&:id)).to eq(expected_pages) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it "has questions" do | ||||||
|  |     expected_questions = %w[tenant_code age1 sex1 other_hhmemb relat2 age2 sex2 ecstat2] | ||||||
|  |     expect(subject.questions.map(&:id)).to eq(expected_questions) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "for a given in progress case log" do | ||||||
|  |     let(:case_log) { FactoryBot.build(:case_log, :in_progress) } | ||||||
|  | 
 | ||||||
|  |     it "has a status" do | ||||||
|  |       expect(subject.status(case_log)).to eq(:in_progress) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has status helpers" do | ||||||
|  |       expect(subject.is_incomplete?(case_log)).to be(true) | ||||||
|  |       expect(subject.is_started?(case_log)).to be(true) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has question helpers for the number of applicable questions" do | ||||||
|  |       expected_questions = %w[tenant_code age1 sex1 other_hhmemb] | ||||||
|  |       expect(subject.applicable_questions(case_log).map(&:id)).to eq(expected_questions) | ||||||
|  |       expect(subject.applicable_questions_count(case_log)).to eq(4) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has question helpers for the number of answered questions" do | ||||||
|  |       expected_questions = %w[tenant_code age1] | ||||||
|  |       expect(subject.answered_questions(case_log).map(&:id)).to eq(expected_questions) | ||||||
|  |       expect(subject.answered_questions_count(case_log)).to eq(2) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has a question helpers for the unanswered questions" do | ||||||
|  |       expected_questions = %w[sex1 other_hhmemb] | ||||||
|  |       expect(subject.unanswered_questions(case_log).map(&:id)).to eq(expected_questions) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context "for a given completed case log" do | ||||||
|  |     let(:case_log) { FactoryBot.build(:case_log, :completed) } | ||||||
|  | 
 | ||||||
|  |     it "has a status" do | ||||||
|  |       expect(subject.status(case_log)).to eq(:completed) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "has status helpers" do | ||||||
|  |       expect(subject.is_incomplete?(case_log)).to be(false) | ||||||
|  |       expect(subject.is_started?(case_log)).to be(true) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
					Loading…
					
					
				
		Reference in new issue