40 changed files with 752 additions and 580 deletions
			
			
		| @ -1,57 +1,20 @@ | ||||
| module CheckAnswersHelper | ||||
|   def total_answered_questions(subsection, case_log, form) | ||||
|     total_questions(subsection, case_log, form).keys.count do |question_key| | ||||
|       case_log[question_key].present? | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def total_number_of_questions(subsection, case_log, form) | ||||
|     total_questions(subsection, case_log, form).count | ||||
|   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 | ||||
|     form.next_page(page_name) | ||||
|   end | ||||
| 
 | ||||
|   def create_update_answer_link(question_title, question_info, case_log, form) | ||||
|     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" | ||||
|   def display_answered_questions_summary(subsection, case_log) | ||||
|     total = subsection.applicable_questions_count(case_log) | ||||
|     answered = subsection.answered_questions_count(case_log) | ||||
|     if total == answered | ||||
|       '<p class="govuk-body govuk-!-margin-bottom-7">You answered all the questions</p>'.html_safe | ||||
|     else | ||||
|                   case_log[question_title].blank? ? "Answer" : "Change" | ||||
|       "<p class=\"govuk-body govuk-!-margin-bottom-7\">You answered #{answered} of #{total} questions</p> | ||||
|       #{create_next_missing_question_link(subsection, case_log)}".html_safe | ||||
|     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) | ||||
|     pages_to_fill_in = [] | ||||
|     form.pages_for_subsection(subsection).each do |page_title, page_info| | ||||
|       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 | ||||
|   end | ||||
| private | ||||
| 
 | ||||
|   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 | ||||
|   def create_next_missing_question_link(subsection, case_log) | ||||
|     pages_to_fill_in = subsection.unanswered_questions(case_log).map(&:page) | ||||
|     url = "/case_logs/#{case_log.id}/#{pages_to_fill_in.first.id}" | ||||
|     link_to("Answer the missing questions", url, class: "govuk-link").html_safe | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,11 +1,9 @@ | ||||
| module ConditionalQuestionsHelper | ||||
|   def conditional_questions_for_page(page) | ||||
|     page["questions"].values.map { |question| | ||||
|       question["conditional_for"] | ||||
|     }.compact.map(&:keys).flatten | ||||
|     page.questions.map(&:conditional_for).compact.map(&:keys).flatten | ||||
|   end | ||||
| 
 | ||||
|   def display_question_key_div(page_info, question_key) | ||||
|     "style='display:none;'".html_safe if conditional_questions_for_page(page_info).include?(question_key) | ||||
|   def display_question_key_div(page, question) | ||||
|     "style='display:none;'".html_safe if conditional_questions_for_page(page).include?(question.id) | ||||
|   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"> | ||||
|   <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> | ||||
|   <dd class="govuk-summary-list__value"> | ||||
|     <%= form.get_answer_label(@case_log, question_title) %> | ||||
|     <%= question.answer_label(@case_log) %> | ||||
|   </dd> | ||||
|   <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> | ||||
| </div> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <%= f.govuk_date_field question_key, | ||||
|     hint: { text: question["hint_text"] }, | ||||
|     legend: { text: question["header"].html_safe, size: "l"}, | ||||
| <%= f.govuk_date_field question.id.to_sym, | ||||
|     hint: { text: question.hint_text }, | ||||
|     legend: { text: question.header.html_safe, size: "l"}, | ||||
|     width: 20, | ||||
|     **stimulus_html_attributes(question) | ||||
| %> | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| <%= f.govuk_number_field question_key, | ||||
|     hint: { text: question["hint_text"] }, | ||||
|     label: { text: question["header"].html_safe, size: "l"}, | ||||
|     min: question["min"], max: question["max"], step: question["step"], | ||||
|     width: 20, :readonly => question["readonly"], | ||||
| <%= f.govuk_number_field question.id.to_sym, | ||||
|     hint: { text: question.hint_text }, | ||||
|     label: { text: question.header.html_safe, size: "l"}, | ||||
|     min: question.min, max: question.max, step: question.step, | ||||
|     width: 20, :readonly => question.read_only?, | ||||
|    **stimulus_html_attributes(question) | ||||
| %> | ||||
|  | ||||
| @ -1,13 +1,13 @@ | ||||
| <%= f.govuk_radio_buttons_fieldset question_key, | ||||
|     legend: { text: question["header"].html_safe, size: "l" }, | ||||
|     hint: { text: question["hint_text"] }, | ||||
|     small: (question["answer_options"].size > 5) do %> | ||||
| <%= f.govuk_radio_buttons_fieldset question.id.to_sym, | ||||
|     legend: { text: question.header.html_safe, size: "l" }, | ||||
|     hint: { text: question.hint_text }, | ||||
|     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") %> | ||||
|         <%= f.govuk_radio_divider %> | ||||
|       <% 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 %> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <%= f.govuk_text_field question_key, | ||||
|     hint: { text: question["hint_text"] }, | ||||
|     label: { text: question["header"].html_safe, size: "l"}, | ||||
| <%= f.govuk_text_field question.id.to_sym, | ||||
|     hint: { text: question.hint_text }, | ||||
|     label: { text: question.header.html_safe, size: "l"}, | ||||
|     width: 20, | ||||
|     **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