From cec1208b35e7aa4e9f3dfbcfdce072cff2444f19 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:02:06 +0100 Subject: [PATCH] CLDC-2290 implement delete multiple logs story (#1657) * add a button to the logs list to delete multiple logs style and position of button helpers for displaying the button conditionally depending on user role and what filters and search are active * correct indentation from 4 spaces to 2 in view file * test appearance of delete logs button on index page for lettings logs * write a happy path feature test for the entire journey * create basic tests for the view component for listing logs to delete * create request tests for the GET delete-logs path * create request tests for the GET delete-logs-confirmation path * create request tests for the DELETE delete-logs path * comprehensive reworking after code review ensure that we are not passing lists of ids through params in the query string, risking overflowing the maximum URL length, adjust tests accordingly, do not attempt to reuse the same table for sales and lettings * alter config to allow creating controllers from the command line with associated spec files that matches how we test * extract controller methods and associated tests to do with the delete logs feature into their own controller, amend routes accordingly * implement same work for sales as for lettings * implement the story for lettings and sales logs under the organisation tab routing and controller methods testing for deleting sales logs, lettings or sales logs for an organisation move storage of relevant routes inside the form object as a comprehensive view model * merge the delete pages for lettings logs and sales logs, add to the tests for the lettings page to test sales specific content * minor refactor to delete logs controller: ensure session filters are only fetched from teh session when needed and extract discard logs method to private method * extract tables for lettings and sales to own partials * refactor delete logs controller after tech review improve the private method that builds the form object so that it has the flexibility to do so for all controller methods ensure that the search term is passed to the delete logs controller when navigating through the organisations tab ensure that noly logs for that organisation are displayed when navigating to delete logs through the organisations tab * remove unnecessary untested arguments * test new helper methods * implement dirty fiddle to get the checkboxes smaller and also not misaligned * ensure delete logs button is always visible on log lists when in the organisations tab * minor linting corrections * revert change, causing errors and outside the scope of this ticket * simplify tests for whether delete logs button appears on index page * replicate request specs from lettings for sales and organisations controllers * minor refactor of lettings log feature spec setup, replicate happy path for sales * minor refactors after rebasing onto Nat's work * temp * write tests for the delete logs form object * lint: add new line at end of file * respond to PO feedback the log id in the delte logs table should be a link to the log the delete logs button should be visible when the user is in a bulk upload journey updated associated tests --- app/controllers/delete_logs_controller.rb | 196 ++++ app/controllers/lettings_logs_controller.rb | 2 +- app/controllers/organisations_controller.rb | 4 +- app/controllers/sales_logs_controller.rb | 1 + app/frontend/styles/_delete-logs-table.scss | 5 + app/frontend/styles/application.scss | 3 +- app/helpers/filters_helper.rb | 12 + app/helpers/log_list_helper.rb | 13 + app/models/forms/delete_logs_form.rb | 44 + .../logs/_delete_logs_table_lettings.html.erb | 30 + .../logs/_delete_logs_table_sales.html.erb | 30 + app/views/logs/_log_list.html.erb | 12 +- app/views/logs/delete_logs.html.erb | 22 + .../logs/delete_logs_confirmation.html.erb | 25 + app/views/logs/index.html.erb | 6 +- app/views/logs/update_logs.html.erb | 65 +- app/views/organisations/logs.html.erb | 2 + config/application.rb | 9 + config/locales/en.yml | 5 + config/routes.rb | 18 + spec/features/lettings_log_spec.rb | 40 + spec/features/sales_log_spec.rb | 73 +- spec/helpers/filters_helper_spec.rb | 78 ++ spec/helpers/log_list_helper_spec.rb | 72 ++ spec/models/forms/delete_logs_form_spec.rb | 89 ++ spec/requests/delete_logs_controller_spec.rb | 946 ++++++++++++++++++ .../requests/lettings_logs_controller_spec.rb | 40 +- .../requests/organisations_controller_spec.rb | 344 ++++--- spec/requests/sales_logs_controller_spec.rb | 32 + spec/views/form/page_view_spec.rb | 4 +- spec/views/logs/delete_logs_spec.rb | 121 +++ 31 files changed, 2115 insertions(+), 228 deletions(-) create mode 100644 app/controllers/delete_logs_controller.rb create mode 100644 app/frontend/styles/_delete-logs-table.scss create mode 100644 app/helpers/log_list_helper.rb create mode 100644 app/models/forms/delete_logs_form.rb create mode 100644 app/views/logs/_delete_logs_table_lettings.html.erb create mode 100644 app/views/logs/_delete_logs_table_sales.html.erb create mode 100644 app/views/logs/delete_logs.html.erb create mode 100644 app/views/logs/delete_logs_confirmation.html.erb create mode 100644 spec/helpers/log_list_helper_spec.rb create mode 100644 spec/models/forms/delete_logs_form_spec.rb create mode 100644 spec/requests/delete_logs_controller_spec.rb create mode 100644 spec/views/logs/delete_logs_spec.rb diff --git a/app/controllers/delete_logs_controller.rb b/app/controllers/delete_logs_controller.rb new file mode 100644 index 000000000..0b4dc3ba5 --- /dev/null +++ b/app/controllers/delete_logs_controller.rb @@ -0,0 +1,196 @@ +class DeleteLogsController < ApplicationController + rescue_from ActiveRecord::RecordNotFound, with: :render_not_found + + before_action :session_filters, if: :current_user, except: %i[discard_lettings_logs discard_sales_logs discard_lettings_logs_for_organisation discard_sales_logs_for_organisation] + before_action :add_organisation_to_filters, only: %i[delete_lettings_logs_for_organisation delete_lettings_logs_for_organisation_with_selected_ids delete_lettings_logs_for_organisation_confirmation delete_sales_logs_for_organisation delete_sales_logs_for_organisation_with_selected_ids delete_sales_logs_for_organisation_confirmation] + + def delete_lettings_logs + @delete_logs_form = delete_logs_form(log_type: :lettings) + render "logs/delete_logs" + end + + def delete_lettings_logs_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :lettings, selected_ids:) + render "logs/delete_logs" + end + + def delete_lettings_logs_confirmation + @delete_logs_form = delete_logs_form(log_type: :lettings, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_lettings_logs + logs = LettingsLog.find(params.require(:ids)) + discard logs + + redirect_to lettings_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_sales_logs + @delete_logs_form = delete_logs_form(log_type: :sales) + render "logs/delete_logs" + end + + def delete_sales_logs_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :sales, selected_ids:) + render "logs/delete_logs" + end + + def delete_sales_logs_confirmation + @delete_logs_form = delete_logs_form(log_type: :sales, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_sales_logs + logs = SalesLog.find(params.require(:ids)) + discard logs + + redirect_to sales_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_lettings_logs_for_organisation + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true) + render "logs/delete_logs" + end + + def delete_lettings_logs_for_organisation_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true, selected_ids:) + render "logs/delete_logs" + end + + def delete_lettings_logs_for_organisation_confirmation + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_lettings_logs_for_organisation + logs = LettingsLog.where(owning_organisation: params[:id]).find(params.require(:ids)) + discard logs + + redirect_to lettings_logs_organisation_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_sales_logs_for_organisation + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true) + render "logs/delete_logs" + end + + def delete_sales_logs_for_organisation_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true, selected_ids:) + render "logs/delete_logs" + end + + def delete_sales_logs_for_organisation_confirmation + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_sales_logs_for_organisation + logs = SalesLog.where(owning_organisation: params[:id]).find(params.require(:ids)) + discard logs + + redirect_to sales_logs_organisation_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + +private + + def session_filters + @session_filters ||= filter_manager.session_filters + end + + def filter_manager + log_type = action_name.include?("lettings") ? "lettings_logs" : "sales_logs" + FilterManager.new(current_user:, session:, params:, filter_type: log_type) + end + + def delete_logs_form(log_type:, organisation: false, selected_ids: nil, form_params: {}) + paths = case log_type + when :lettings + organisation ? lettings_logs_for_organisation_paths : lettings_logs_paths + when :sales + organisation ? sales_logs_for_organisation_paths : sales_logs_paths + end + attributes = { + log_type:, + current_user:, + log_filters: @session_filters, + search_term:, + selected_ids:, + **paths, + }.merge(form_params).transform_keys(&:to_sym) + Forms::DeleteLogsForm.new(attributes) + end + + def form_params + form_attributes = params.require(:forms_delete_logs_form).permit(:search_term, selected_ids: []) + form_attributes[:selected_ids] = [] unless form_attributes.key? :selected_ids + form_attributes + end + + def lettings_logs_paths + { + delete_confirmation_path: delete_logs_confirmation_lettings_logs_path, + back_to_logs_path: lettings_logs_path(search: search_term), + delete_path: delete_logs_lettings_logs_path, + } + end + + def sales_logs_paths + { + delete_confirmation_path: delete_logs_confirmation_sales_logs_path, + back_to_logs_path: sales_logs_path(search: search_term), + delete_path: delete_logs_sales_logs_path, + } + end + + def lettings_logs_for_organisation_paths + { + delete_confirmation_path: delete_lettings_logs_confirmation_organisation_path, + back_to_logs_path: lettings_logs_organisation_path(search: search_term), + delete_path: delete_lettings_logs_organisation_path, + } + end + + def sales_logs_for_organisation_paths + { + delete_confirmation_path: delete_sales_logs_confirmation_organisation_path, + back_to_logs_path: sales_logs_organisation_path(search: search_term), + delete_path: delete_sales_logs_organisation_path, + } + end + + def add_organisation_to_filters + @session_filters[:organisation] = params[:id] + end + + def search_term + params["search"] + end + + def selected_ids + params.require(:selected_ids).split.map(&:to_i) + end + + def discard(logs) + logs.each do |log| + authorize log, :destroy? + log.discard! + end + end +end diff --git a/app/controllers/lettings_logs_controller.rb b/app/controllers/lettings_logs_controller.rb index eb1caa47c..f67e94acc 100644 --- a/app/controllers/lettings_logs_controller.rb +++ b/app/controllers/lettings_logs_controller.rb @@ -16,7 +16,7 @@ class LettingsLogsController < LogsController all_logs = current_user.lettings_logs.visible unpaginated_filtered_logs = filter_manager.filtered_logs(all_logs, search_term, session_filters) - @search_term = search_term + @delete_logs_path = delete_logs_lettings_logs_path(search: search_term) @pagy, @logs = pagy(unpaginated_filtered_logs) @searched = search_term.presence @total_count = all_logs.size diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 7fdcd1d94..98082bea9 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -96,6 +96,7 @@ class OrganisationsController < ApplicationController format.html do @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) + @delete_logs_path = delete_lettings_logs_organisation_path(search: @search_term) @searched = search_term.presence @total_count = organisation_logs.size @log_type = :lettings @@ -119,13 +120,14 @@ class OrganisationsController < ApplicationController end def sales_logs - organisation_logs = SalesLog.where(owning_organisation_id: @organisation.id) + organisation_logs = SalesLog.visible.where(owning_organisation_id: @organisation.id) unpaginated_filtered_logs = filter_manager.filtered_logs(organisation_logs, search_term, session_filters) respond_to do |format| format.html do @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) + @delete_logs_path = delete_sales_logs_organisation_path(search: @search_term) @searched = search_term.presence @total_count = organisation_logs.size @log_type = :sales diff --git a/app/controllers/sales_logs_controller.rb b/app/controllers/sales_logs_controller.rb index 27f3d4f05..4a06fa6c4 100644 --- a/app/controllers/sales_logs_controller.rb +++ b/app/controllers/sales_logs_controller.rb @@ -18,6 +18,7 @@ class SalesLogsController < LogsController all_logs = current_user.sales_logs.visible unpaginated_filtered_logs = filter_manager.filtered_logs(all_logs, search_term, session_filters) + @delete_logs_path = delete_logs_sales_logs_path(search: search_term) @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) @searched = search_term.presence diff --git a/app/frontend/styles/_delete-logs-table.scss b/app/frontend/styles/_delete-logs-table.scss new file mode 100644 index 000000000..00bb3ed28 --- /dev/null +++ b/app/frontend/styles/_delete-logs-table.scss @@ -0,0 +1,5 @@ +.checkbox-cell { + .govuk-checkboxes__item { + margin-top: -7px; + } +} diff --git a/app/frontend/styles/application.scss b/app/frontend/styles/application.scss index 03a6ab6f2..ddf368807 100644 --- a/app/frontend/styles/application.scss +++ b/app/frontend/styles/application.scss @@ -44,6 +44,7 @@ $govuk-breakpoints: ( @import "search"; @import "sub-navigation"; @import "errors"; +@import "delete-logs-table"; // App utilities .app-\!-colour-muted { @@ -52,7 +53,7 @@ $govuk-breakpoints: ( } .app-\!-colour-red { - color: govuk-colour("red"); + color: govuk-colour("red") !important; } .app-\!-font-tabular { diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index 51472b299..34e768603 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -11,6 +11,18 @@ module FiltersHelper selected_filters[filter].include?(value.to_s) end + def any_filter_selected?(filter_type) + filters_json = session[session_name_for(filter_type)] + return false unless filters_json + + filters = JSON.parse(filters_json) + filters["user"] == "yours" || + filters["organisation"].present? || + filters["status"]&.compact_blank&.any? || + filters["years"]&.compact_blank&.any? || + filters["bulk_upload_id"].present? + end + def status_filters { "not_started" => "Not started", diff --git a/app/helpers/log_list_helper.rb b/app/helpers/log_list_helper.rb new file mode 100644 index 000000000..b3e967358 --- /dev/null +++ b/app/helpers/log_list_helper.rb @@ -0,0 +1,13 @@ +module LogListHelper + def display_delete_logs?(current_user, search_term, filter_type) + if current_user.data_provider? + filter_selected?("user", "yours", filter_type) + else + any_filter_selected?(filter_type) || search_term.present? + end + end + + def in_organisations_tab? + controller.class.name.start_with? "Organisation" + end +end diff --git a/app/models/forms/delete_logs_form.rb b/app/models/forms/delete_logs_form.rb new file mode 100644 index 000000000..6c4d8eba6 --- /dev/null +++ b/app/models/forms/delete_logs_form.rb @@ -0,0 +1,44 @@ +module Forms + class DeleteLogsForm + include ActiveModel::Model + include ActiveModel::Validations + + attr_reader :logs, :log_type, :selected_ids, :search_term, :delete_confirmation_path, :back_to_logs_path, :delete_path + + validate :at_least_one_log_selected + + def initialize(attributes) + @log_type = attributes[:log_type] + @search_term = attributes[:search_term] + @current_user = attributes[:current_user] + @logs = FilterManager.filter_logs(visible_logs, @search_term, attributes[:log_filters], nil, @current_user) + @selected_ids = attributes[:selected_ids] || @logs.map(&:id) + @delete_confirmation_path = attributes[:delete_confirmation_path] + @back_to_logs_path = attributes[:back_to_logs_path] + @delete_path = attributes[:delete_path] + end + + def log_count + @logs.count + end + + def table_partial_name + "logs/delete_logs_table_#{@log_type}" + end + + private + + def at_least_one_log_selected + if selected_ids.blank? || selected_ids.reject(&:blank?).blank? + errors.add(:log_ids, "Select at least one log to delete or press cancel to return") + end + end + + def visible_logs + case @log_type + when :lettings then @current_user.lettings_logs.visible + when :sales then @current_user.sales_logs.visible + end + end + end +end diff --git a/app/views/logs/_delete_logs_table_lettings.html.erb b/app/views/logs/_delete_logs_table_lettings.html.erb new file mode 100644 index 000000000..bd5952170 --- /dev/null +++ b/app/views/logs/_delete_logs_table_lettings.html.erb @@ -0,0 +1,30 @@ +<%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell header: true, text: "Log ID" %> + <% row.cell header: true, text: "Tenancy code" %> + <% row.cell header: true, text: "Property reference" %> + <% row.cell header: true, text: "Status" %> + <% row.cell header: true, text: "Delete?" %> + <% end %> + <% end %> + <% table.body do |body| %> + <% f.govuk_check_boxes_fieldset :selected_ids, small: true do %> + <% delete_logs_form.logs.each do |log| %> + <% body.row do |row| %> + <% row.cell do %> + <%= govuk_link_to log.id, url_for(log) %> + <% end %> + <% row.cell text: log.tenancycode %> + <% row.cell text: log.propcode %> + <% row.cell text: status_tag(log.status) %> + <% row.cell html_attributes: { class: "checkbox-cell" } do %> + <% f.govuk_check_box :selected_ids, log.id, + label: { text: log.id, hidden: true }, + checked: delete_logs_form.selected_ids.include?(log.id) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/logs/_delete_logs_table_sales.html.erb b/app/views/logs/_delete_logs_table_sales.html.erb new file mode 100644 index 000000000..8659f12bb --- /dev/null +++ b/app/views/logs/_delete_logs_table_sales.html.erb @@ -0,0 +1,30 @@ +<%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell header: true, text: "Log ID" %> + <% row.cell header: true, text: "Purchaser code" %> + <% row.cell header: true, text: "Sale completion date" %> + <% row.cell header: true, text: "Status" %> + <% row.cell header: true, text: "Delete?" %> + <% end %> + <% end %> + <% table.body do |body| %> + <% f.govuk_check_boxes_fieldset :selected_ids, small: true do %> + <% delete_logs_form.logs.each do |log| %> + <% body.row do |row| %> + <% row.cell do %> + <%= govuk_link_to log.id, url_for(log) %> + <% end %> + <% row.cell text: log.purchid %> + <% row.cell text: log.saledate&.to_formatted_s(:govuk_date) %> + <% row.cell text: status_tag(log.status) %> + <% row.cell html_attributes: { class: "checkbox-cell" } do %> + <% f.govuk_check_box :selected_ids, log.id, + label: { text: log.id, hidden: true }, + checked: delete_logs_form.selected_ids.include?(log.id) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/logs/_log_list.html.erb b/app/views/logs/_log_list.html.erb index e92186d4d..d41bdc9a8 100644 --- a/app/views/logs/_log_list.html.erb +++ b/app/views/logs/_log_list.html.erb @@ -1,11 +1,21 @@

+
+
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %> <% if logs&.any? %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <% if @current_user.support? %> - <%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv" %> + <%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <% end %> <% end %> +
+
+ <% if logs&.any? && (display_delete_logs?(@current_user, searched, filter_type) || in_organisations_tab?) %> + <%# remove @ on current user %> + <%= govuk_link_to "Delete logs", delete_logs_path, class: "app-!-colour-red" %> + <% end %> +
+

<% logs.map do |log| %> <%= render(LogSummaryComponent.new(current_user:, log:)) %> diff --git a/app/views/logs/delete_logs.html.erb b/app/views/logs/delete_logs.html.erb new file mode 100644 index 000000000..8f9338ff2 --- /dev/null +++ b/app/views/logs/delete_logs.html.erb @@ -0,0 +1,22 @@ +<% title = "Delete logs" %> +<% content_for :title, title %> +<% content_for :before_content do %> + <%= govuk_back_link(href: :back) %> +<% end %> + +

+ <%= title %> + Review the logs you want to delete +

+

You've selected <%= @delete_logs_form.log_count %> <%= "log".pluralize(@delete_logs_form.log_count) %> to delete

+ +
+ <%= form_with model: @delete_logs_form, url: @delete_logs_form.delete_confirmation_path do |f| %> + <%= f.hidden_field :search_term, value: @delete_logs_form.search_term %> + <%= f.govuk_error_summary %> + <%= render partial: @delete_logs_form.table_partial_name, locals: { f:, delete_logs_form: @delete_logs_form } %> + <%= f.govuk_submit "Continue" do %> + <%= govuk_button_link_to "Cancel", @delete_logs_form.back_to_logs_path, secondary: true %> + <% end %> + <% end %> +
diff --git a/app/views/logs/delete_logs_confirmation.html.erb b/app/views/logs/delete_logs_confirmation.html.erb new file mode 100644 index 000000000..a9b4252d4 --- /dev/null +++ b/app/views/logs/delete_logs_confirmation.html.erb @@ -0,0 +1,25 @@ +<% title = "Delete logs" %> +<% content_for :title, title %> +<% content_for :before_content do %> + <%= govuk_back_link(href: :back) %> +<% end %> + +

+ <%= title %> + Are you sure you want to delete these logs? +

+<% log_count = @delete_logs_form.selected_ids.count %> +

You've selected <%= log_count %> <%= "log".pluralize(log_count) %> to delete

+ +<%= govuk_warning_text(icon_fallback_text: "Danger") do %> + You will not be able to undo this action +<% end %> + +
+ <%= govuk_button_to "Delete logs", @delete_logs_form.delete_path, method: "delete", params: { ids: @delete_logs_form.selected_ids } %> + <%= form_with url: @delete_logs_form.delete_path do |f| %> + <%= f.hidden_field :selected_ids, value: @delete_logs_form.selected_ids %> + <%= f.hidden_field :search, value: @delete_logs_form.search_term %> + <%= f.govuk_submit "Cancel", secondary: true %> + <% end %> +
diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index e2cc2e0d1..eac53ed7c 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -68,8 +68,10 @@ searched: @searched, item_label:, total_count: @total_count, - csv_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: false), - csv_codes_only_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: true), + csv_download_url: csv_download_url_for_controller(controller:, search: @searched, codes_only: false), + csv_codes_only_download_url: csv_download_url_for_controller(controller:, search: @searched, codes_only: true), + delete_logs_path: @delete_logs_path, + filter_type: @filter_type, } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/app/views/logs/update_logs.html.erb b/app/views/logs/update_logs.html.erb index 1b744beb6..d89ecdc4b 100644 --- a/app/views/logs/update_logs.html.erb +++ b/app/views/logs/update_logs.html.erb @@ -4,43 +4,42 @@ <% content_for :title, title %> <% if @total_count < 1 %> - <%= render partial: "organisations/headings", locals: { main: "There are no more logs that need updating", sub: "" } %> -

- You’ve completed all the logs that were affected by scheme changes. -

-
- <%= govuk_button_link_to "Back to all logs", lettings_logs_path %> -
+ <%= render partial: "organisations/headings", locals: { main: "There are no more logs that need updating", sub: "" } %> +

+ You’ve completed all the logs that were affected by scheme changes. +

+
+ <%= govuk_button_link_to "Back to all logs", lettings_logs_path %> +
<% else %> - <%= render partial: "organisations/headings", locals: { main: "You need to update #{@total_count} logs", sub: "" } %> - - <%= govuk_table do |table| %> - <% table.head do |head| %> - <% head.row do |row| %> - <% row.cell(header: true, text: "Log ID") %> - <% row.cell(header: true, text: "Tenancy code") %> - <% row.cell(header: true, text: "Property reference") %> - <% row.cell(header: true, text: "Status") %> - <% row.cell(header: true, text: "") %> - <% end %> - <% end %> + <%= render partial: "organisations/headings", locals: { main: "You need to update #{@total_count} logs", sub: "" } %> + <%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell(header: true, text: "Log ID") %> + <% row.cell(header: true, text: "Tenancy code") %> + <% row.cell(header: true, text: "Property reference") %> + <% row.cell(header: true, text: "Status") %> + <% row.cell(header: true, text: "") %> + <% end %> + <% end %> <% @logs.each do |log| %> - <% table.body do |body| %> - <% body.row do |row| %> - <% row.cell(text: log.id) %> - <% row.cell(text: log.tenancycode) %> - <% row.cell(text: log.propcode) %> - <% row.cell(text: status_tag(log.status)) %> - <% row.cell(html_attributes: { - scope: "row", - class: "govuk-!-text-align-right", - }) do %> - <%= govuk_link_to("Update now", send(log.form.unresolved_log_path, log)) %> - <% end %> - <% end %> + <% table.body do |body| %> + <% body.row do |row| %> + <% row.cell(text: log.id) %> + <% row.cell(text: log.tenancycode) %> + <% row.cell(text: log.propcode) %> + <% row.cell(text: status_tag(log.status)) %> + <% row.cell(html_attributes: { + scope: "row", + class: "govuk-!-text-align-right", + }) do %> + <%= govuk_link_to "Update now", send(log.form.unresolved_log_path, log) %> + <% end %> <% end %> + <% end %> <% end %> - <% end %> + <% end %> <% end %> <%= render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/app/views/organisations/logs.html.erb b/app/views/organisations/logs.html.erb index 6b269590c..89f54fb1d 100644 --- a/app/views/organisations/logs.html.erb +++ b/app/views/organisations/logs.html.erb @@ -34,6 +34,8 @@ total_count: @total_count, csv_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: false), csv_codes_only_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: true), + delete_logs_path: @delete_logs_path, + filter_type: @filter_type, } %> <%= render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/config/application.rb b/config/application.rb index 49a236636..4fd65c695 100644 --- a/config/application.rb +++ b/config/application.rb @@ -41,5 +41,14 @@ module DataCollector key: "_data_collector_session", secure: (Rails.env.production? || Rails.env.staging? || Rails.env.review?) ) + + config.generators do |g| + g.test_framework :rspec, + request_specs: true, + view_specs: false, + routing_specs: false, + helper_specs: false, + controller_specs: false + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index f5d8a56b7..e1635e748 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -175,6 +175,11 @@ en: new_organisation_telephone_number: blank: "Enter a valid telephone number" + notification: + logs_deleted: + one: "%{count} log has been deleted" + other: "%{count} logs have been deleted" + validations: organisation: data_sharing_agreement_not_signed: Your organisation must accept the Data Sharing Agreement before you can create any logs. diff --git a/config/routes.rb b/config/routes.rb index 9dd9b7325..fa0cb598e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -122,7 +122,15 @@ Rails.application.routes.draw do get "users", to: "organisations#users" get "users/invite", to: "users/account#new" get "lettings-logs", to: "organisations#lettings_logs" + get "delete-lettings-logs", to: "delete_logs#delete_lettings_logs_for_organisation" + post "delete-lettings-logs", to: "delete_logs#delete_lettings_logs_for_organisation_with_selected_ids" + post "delete-lettings-logs-confirmation", to: "delete_logs#delete_lettings_logs_for_organisation_confirmation" + delete "delete-lettings-logs", to: "delete_logs#discard_lettings_logs_for_organisation" get "sales-logs", to: "organisations#sales_logs" + get "delete-sales-logs", to: "delete_logs#delete_sales_logs_for_organisation" + post "delete-sales-logs", to: "delete_logs#delete_sales_logs_for_organisation_with_selected_ids" + post "delete-sales-logs-confirmation", to: "delete_logs#delete_sales_logs_for_organisation_confirmation" + delete "delete-sales-logs", to: "delete_logs#discard_sales_logs_for_organisation" get "lettings-logs/csv-download", to: "organisations#download_lettings_csv" post "lettings-logs/email-csv", to: "organisations#email_lettings_csv" get "lettings-logs/csv-confirmation", to: "lettings_logs#csv_confirmation" @@ -170,6 +178,11 @@ Rails.application.routes.draw do post "email-csv", to: "lettings_logs#email_csv" get "csv-confirmation", to: "lettings_logs#csv_confirmation" + get "delete-logs", to: "delete_logs#delete_lettings_logs" + post "delete-logs", to: "delete_logs#delete_lettings_logs_with_selected_ids" + post "delete-logs-confirmation", to: "delete_logs#delete_lettings_logs_confirmation" + delete "delete-logs", to: "delete_logs#discard_lettings_logs" + resources :bulk_upload_lettings_logs, path: "bulk-upload-logs", only: %i[show update] do collection do get :start @@ -227,6 +240,11 @@ Rails.application.routes.draw do post "email-csv", to: "sales_logs#email_csv" get "csv-confirmation", to: "sales_logs#csv_confirmation" + get "delete-logs", to: "delete_logs#delete_sales_logs" + post "delete-logs", to: "delete_logs#delete_sales_logs_with_selected_ids" + post "delete-logs-confirmation", to: "delete_logs#delete_sales_logs_confirmation" + delete "delete-logs", to: "delete_logs#discard_sales_logs" + resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do collection do get :start diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index 7f1e77d2a..2e83b669c 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -220,6 +220,46 @@ RSpec.describe "Lettings Log Features" do end end end + + it "is possible to delete multiple logs" do + postcode = "SW1A 1AA" + lettings_log_1 = create(:lettings_log, :setup_completed, created_by: support_user, postcode_full: postcode) + lettings_log_2 = create(:lettings_log, :in_progress, created_by: support_user, postcode_full: postcode) + create_list(:lettings_log, 5, :in_progress) + + visit lettings_logs_path + expect(page).to have_selector "article.app-log-summary", count: 7 + expect(page).not_to have_link "Delete logs" + within ".app-filter" do + check "status-in-progress-field" + choose "user-yours-field" + click_button + end + expect(page).to have_selector "article.app-log-summary", count: 2 + expect(page).to have_link "Delete logs" + click_link "Delete logs" + + expect(page).to have_current_path delete_logs_lettings_logs_path + rows = page.find_all "tbody tr" + expect(rows.count).to be 2 + id_to_delete, id_to_keep = rows.map { |row| row.first("td").text.to_i } + expect([id_to_delete, id_to_keep]).to match_array [lettings_log_1.id, lettings_log_2.id] + check "forms-delete-logs-form-selected-ids-#{id_to_delete}-field" + uncheck "forms-delete-logs-form-selected-ids-#{id_to_keep}-field" + click_button "Continue" + + expect(page).to have_current_path delete_logs_confirmation_lettings_logs_path + expect(page.text).to include "You've selected 1 log to delete" + expect(page.find("form.button_to")[:action]).to eq delete_logs_lettings_logs_path + click_button "Delete logs" + + expect(page).to have_current_path lettings_logs_path + expect(page).to have_selector "article.app-log-summary", count: 1 + expect(page.find("article.app-log-summary h2").text).to eq "Log #{id_to_keep}" + deleted_log = LettingsLog.find(id_to_delete) + expect(deleted_log.status).to eq "deleted" + expect(deleted_log.discarded_at).not_to be nil + end end context "when the signed is user is not a Support user" do diff --git a/spec/features/sales_log_spec.rb b/spec/features/sales_log_spec.rb index 8093f85dd..890c82786 100644 --- a/spec/features/sales_log_spec.rb +++ b/spec/features/sales_log_spec.rb @@ -3,16 +3,14 @@ require "rails_helper" RSpec.describe "Sales Log Features" do context "when searching for specific sales logs" do context "when I am signed in and there are sales logs in the database" do - let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now) } + let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now, name: "Jimbo") } let!(:log_to_search) { FactoryBot.create(:sales_log, owning_organisation: user.organisation) } let!(:same_organisation_log) { FactoryBot.create(:sales_log, owning_organisation: user.organisation) } let!(:another_organisation_log) { FactoryBot.create(:sales_log) } before do - visit("/sales-logs") - fill_in("user[email]", with: user.email) - fill_in("user[password]", with: user.password) - click_button("Sign in") + sign_in user + visit sales_logs_path end it "displays the logs belonging to the same organisation" do @@ -23,34 +21,75 @@ RSpec.describe "Sales Log Features" do context "when returning to the list of logs via breadcrumbs link" do before do - visit("/sales-logs") click_button("Create a new sales log") click_link("Logs") end it "navigates you to the sales logs page" do - expect(page).to have_current_path("/sales-logs") + expect(page).to have_current_path sales_logs_path end end context "when completing the setup sales log section" do it "includes the purchaser code and sale completion date questions" do - visit("/sales-logs") - click_button("Create a new sales log") - click_link("Set up this sales log") + click_button "Create a new sales log" + click_link "Set up this sales log" fill_in("sales_log[saledate(1i)]", with: Time.zone.today.year) fill_in("sales_log[saledate(2i)]", with: Time.zone.today.month) fill_in("sales_log[saledate(3i)]", with: Time.zone.today.day) - click_button("Save and continue") - fill_in("sales_log[purchid]", with: "PC123") - click_button("Save and continue") + click_button "Save and continue" + fill_in "sales_log[purchid]", with: "PC123" + click_button "Save and continue" log_id = page.current_path.scan(/\d/).join - visit("sales-logs/#{log_id}/setup/check-answers") - expect(page).to have_content("Sale completion date") + visit sales_log_setup_check_answers_path(log_id) + expect(page).to have_content "Sale completion date" expect(page).to have_content(Time.zone.today.year) - expect(page).to have_content("Purchaser code") - expect(page).to have_content("PC123") + expect(page).to have_content "Purchaser code" + expect(page).to have_content "PC123" + end + end + + it "is possible to delete multiple logs" do + log_card_selector = "article.app-log-summary" + logs_by_user = create_list(:sales_log, 2, created_by: user) + + visit sales_logs_path + expect(page).to have_selector log_card_selector, count: 4 + expect(page).not_to have_link "Delete logs" + + within ".app-filter" do + choose "user-yours-field" + click_button end + + expect(page).to have_selector log_card_selector, count: 2 + expect(page).to have_link "Delete logs" + + click_link "Delete logs" + + expect(page).to have_current_path delete_logs_sales_logs_path + + rows = page.find_all "tbody tr" + expect(rows.count).to be 2 + id_to_delete, id_to_keep = rows.map { |row| row.first("td").text.to_i } + expect([id_to_delete, id_to_keep]).to match_array logs_by_user.map(&:id) + check "forms-delete-logs-form-selected-ids-#{id_to_delete}-field" + uncheck "forms-delete-logs-form-selected-ids-#{id_to_keep}-field" + click_button "Continue" + + expect(page).to have_current_path delete_logs_confirmation_sales_logs_path + expect(page.text).to include "You've selected 1 log to delete" + button = page.find("form.button_to") + expect(button[:action]).to eq delete_logs_sales_logs_path + expect(button.text).to eq "Delete logs" + click_button "Delete logs" + + expect(page).to have_current_path sales_logs_path + expect(page).to have_selector "article.app-log-summary", count: 1 + expect(page.find("article.app-log-summary h2").text).to eq "Log #{id_to_keep}" + deleted_log = SalesLog.find(id_to_delete) + expect(deleted_log.status).to eq "deleted" + expect(deleted_log.discarded_at).not_to be nil end end end diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb index dfc6b51fb..622fd71f8 100644 --- a/spec/helpers/filters_helper_spec.rb +++ b/spec/helpers/filters_helper_spec.rb @@ -70,6 +70,84 @@ RSpec.describe FiltersHelper do end end + describe "#any_filter_selected?" do + let(:filter_type) { "lettings_logs" } + let(:result) { any_filter_selected?(filter_type) } + let(:serialised_filters) { filters&.to_json } + let(:filters) { nil } + + before do + session[:lettings_logs_filters] = serialised_filters if serialised_filters + end + + it "returns false if the session contains no filters" do + expect(result).to be_falsey + end + + context "when organisation and user are set to all" do + let(:filters) { { "organisation_select" => "all", "user" => "all" } } + + it "returns false" do + expect(result).to be_falsey + end + end + + context "when user is set to 'yours'" do + let(:filters) { { "user" => "yours" } } + + it "returns true" do + expect(result).to be true + end + end + + context "when organisation is filtered" do + let(:filters) { { "organisation" => 2 } } + + it "returns true" do + expect(result).to be true + end + end + + context "when status is filtered" do + let(:filters) { { "status" => %w[in_progress] } } + + it "returns true" do + expect(result).to be true + end + end + + context "when collection year is filtered" do + let(:filters) { { "years" => %w[2023] } } + + it "returns true" do + expect(result).to be true + end + end + + context "when the user is currently in a bulk upload journey" do + let(:filters) { { "bulk_upload_id" => "3456" } } + + it "returns true" do + expect(result).to be true + end + end + + context "when a range of filters are applied" do + let(:filters) do + { + "user" => "all", + "status" => %w[in_progress completed], + "years" => [""], + "organisation" => 2, + } + end + + it "returns true" do + expect(result).to be true + end + end + end + describe "#selected_option" do before do session[:lettings_logs_filters] = {}.to_json diff --git a/spec/helpers/log_list_helper_spec.rb b/spec/helpers/log_list_helper_spec.rb new file mode 100644 index 000000000..22836ec45 --- /dev/null +++ b/spec/helpers/log_list_helper_spec.rb @@ -0,0 +1,72 @@ +require "rails_helper" + +RSpec.describe LogListHelper, type: :helper do + include FiltersHelper + + describe "#display_delete_logs?" do + let(:search_term) { nil } + let(:filter_type) { "lettings_logs" } + + context "when logged in as a data provider" do + let(:result) { display_delete_logs?(user, search_term, filter_type) } + let(:user) { create(:user) } + + it "returns false if no filters or search are set" do + allow(self).to receive(:filter_selected?).and_return false + expect(result).to be false + end + + it "returns true if the user filter is set to 'yours'" do + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return true + expect(result).to be true + end + + it "returns false if any filters other than the user filter are set" do + allow(self).to receive(:filter_selected?).and_return true + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return false + expect(result).to be false + end + + context "when there is a search term present" do + let(:search_term) { "word" } + + it "still returns false as long as the user filter is not set to yours" do + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return false + expect(result).to be false + end + end + end + + context "when logged in as a support user or data coordinator" do + let(:support_user) { create(:user, :support) } + let(:data_coordinator) { create(:user, :data_coordinator) } + let(:support_result) { display_delete_logs?(support_user, search_term, filter_type) } + let(:coordinator_result) { display_delete_logs?(data_coordinator, search_term, filter_type) } + let(:results) { [support_result, coordinator_result] } + + it "returns false if no filters or search are set" do + allow(self).to receive(:any_filter_selected?).and_return false + expect(results).to all be false + end + + it "returns true if any filter is set" do + allow(self).to receive(:any_filter_selected?).and_return true + expect(results).to all be true + end + + context "when there is a search term present" do + let(:search_term) { "word" } + + it "returns true if no filter is selected" do + allow(self).to receive(:any_filter_selected?).and_return false + expect(results).to all be true + end + + it "returns true if any filter is selected" do + allow(self).to receive(:any_filter_selected?).and_return true + expect(results).to all be true + end + end + end + end +end diff --git a/spec/models/forms/delete_logs_form_spec.rb b/spec/models/forms/delete_logs_form_spec.rb new file mode 100644 index 000000000..adad36d90 --- /dev/null +++ b/spec/models/forms/delete_logs_form_spec.rb @@ -0,0 +1,89 @@ +require "rails_helper" + +RSpec.describe Forms::DeleteLogsForm do + let(:delete_logs_form) { described_class.new(attributes) } + + let(:attributes) do + { + log_type:, + search_term:, + current_user:, + log_filters:, + selected_ids:, + delete_confirmation_path:, + back_to_logs_path:, + delete_path:, + } + end + let(:log_type) { :lettings } + let(:search_term) { "meaning" } + let(:current_user) { create(:user) } + let(:log_filters) do + { + "years" => [""], + "status" => ["", "completed"], + "user" => "yours", + } + end + let(:selected_ids) { [visible_logs.first.id] } + let(:delete_confirmation_path) { "/lettings-logs/delete-logs-confirmation" } + let(:back_to_logs_path) { "/lettings-logs?search=meaning" } + let(:delete_path) { "/lettings-logs/delete-logs" } + + let(:visible_logs) { create_list(:lettings_log, 3, created_by: current_user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return visible_logs + end + + it "exposes the log type" do + expect(delete_logs_form.log_type).to be log_type + end + + it "exposes the search term" do + expect(delete_logs_form.search_term).to be search_term + end + + it "exposes the paths" do + expect(delete_logs_form.delete_confirmation_path).to be delete_confirmation_path + expect(delete_logs_form.back_to_logs_path).to be back_to_logs_path + expect(delete_logs_form.delete_path).to be delete_path + end + + it "exposes the logs returned by the filter manager" do + expect(delete_logs_form.logs).to be visible_logs + end + + it "exposes the selected ids" do + expect(delete_logs_form.selected_ids).to be selected_ids + end + + context "when selected ids are not provided to the initializer" do + let(:selected_ids) { nil } + + it "sets the selected ids to be all logs" do + expect(delete_logs_form.selected_ids).to match_array visible_logs.map(&:id) + end + end + + it "calls the filter manager with the correct arguments" do + create(:lettings_log) + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3, arg4, arg5| + expect(arg1).to contain_exactly(*visible_logs) + expect(arg2).to eq search_term + expect(arg3).to eq log_filters + expect(arg4).to be nil + expect(arg5).to be current_user + }.and_return visible_logs + delete_logs_form + end + + it "exposes the number of logs" do + expect(delete_logs_form.log_count).to be visible_logs.count + end + + it "provides the name of the table partial relevant to the log type" do + expect(delete_logs_form.table_partial_name).to eq "logs/delete_logs_table_lettings" + end +end diff --git a/spec/requests/delete_logs_controller_spec.rb b/spec/requests/delete_logs_controller_spec.rb new file mode 100644 index 000000000..fd509d699 --- /dev/null +++ b/spec/requests/delete_logs_controller_spec.rb @@ -0,0 +1,946 @@ +require "rails_helper" + +RSpec.describe "DeleteLogs", type: :request do + let(:page) { Capybara::Node::Simple.new(response.body) } + let(:user) { create(:user, name: "Richard MacDuff") } + + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in user + end + + describe "GET lettings-logs/delete-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let!(:log_2) { create(:lettings_log, :completed, created_by: user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return LettingsLog.all + + get delete_logs_lettings_logs_path(search:) + end + + it "displays the logs returned by the filter service" do + get delete_logs_lettings_logs_path + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_logs_lettings_logs_path + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST lettings-logs/delete-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let!(:log_2) { create(:lettings_log, :completed, created_by: user) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_logs_lettings_logs_path }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return LettingsLog.all + + post delete_logs_lettings_logs_path(search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_logs_lettings_logs_path(selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_logs_lettings_logs_path(selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST lettings-logs/delete-logs-confirmation" do + let(:log_1) { create(:lettings_log, :in_progress) } + let(:log_2) { create(:lettings_log, :completed) } + let(:log_3) { create(:lettings_log, :in_progress) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_logs_confirmation_lettings_logs_path, params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_logs_confirmation_lettings_logs_path }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_logs_lettings_logs_path + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_logs_lettings_logs_path + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :lettings, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_logs_confirmation_lettings_logs_path, params: params + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE lettings-logs/delete-logs" do + let(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + context "when the user is authorized to delete the logs provided" do + let(:log_2) { create(:lettings_log, :completed, created_by: user) } + + it "deletes the logs provided" do + delete delete_logs_lettings_logs_path, params: params + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the lettings log index and displays a notice that the logs have been deleted" do + delete delete_logs_lettings_logs_path, params: params + expect(response).to redirect_to lettings_logs_path + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + context "when the user is not authorized to delete all the logs provided" do + let(:log_2) { create(:lettings_log, :completed) } + + it "returns unauthorised and only deletes logs for which the user is authorised" do + delete delete_logs_lettings_logs_path, params: params + expect(response).to have_http_status(:unauthorized) + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.discarded_at).to be nil + end + end + end + + describe "GET sales-logs/delete-logs" do + let!(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let!(:log_2) { create(:sales_log, :completed, created_by: user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return SalesLog.all + + get delete_logs_sales_logs_path(search:) + end + + it "displays the logs returned by the filter service" do + get delete_logs_sales_logs_path + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_logs_sales_logs_path + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST sales-logs/delete-logs" do + let!(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let!(:log_2) { create(:sales_log, :completed, created_by: user) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_logs_sales_logs_path }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return SalesLog.all + + post delete_logs_sales_logs_path(search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_logs_sales_logs_path(selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_logs_sales_logs_path(selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST sales-logs/delete-logs-confirmation" do + let(:log_1) { create(:sales_log, :in_progress) } + let(:log_2) { create(:sales_log, :completed) } + let(:log_3) { create(:sales_log, :in_progress) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_logs_confirmation_sales_logs_path, params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_logs_confirmation_sales_logs_path }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_logs_sales_logs_path + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_logs_sales_logs_path + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :sales, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_logs_confirmation_sales_logs_path, params: params + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE sales-logs/delete-logs" do + let(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + context "when the user is authorized to delete the logs provided" do + let(:log_2) { create(:sales_log, :completed, created_by: user) } + + it "deletes the logs provided" do + delete delete_logs_sales_logs_path, params: params + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the sales log index and displays a notice that the logs have been deleted" do + delete delete_logs_sales_logs_path, params: params + expect(response).to redirect_to sales_logs_path + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + context "when the user is not authorized to delete all the logs provided" do + let(:log_2) { create(:sales_log, :completed) } + + it "returns unauthorised and only deletes logs for which the user is authorised" do + delete delete_logs_sales_logs_path, params: params + expect(response).to have_http_status(:unauthorized) + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.discarded_at).to be nil + end + end + end + + context "when a support user navigates to the organisations tab" do + let(:organisation) { create(:organisation, name: "Schmorganisation") } + let(:user) { create(:user, :support, name: "Urban Chronotis") } + + describe "GET organisations/delete-lettings-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return LettingsLog.all + + get delete_lettings_logs_organisation_path(id: organisation, search:) + end + + it "displays the logs returned by the filter service" do + get delete_lettings_logs_organisation_path(id: organisation) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_lettings_logs_organisation_path(id: organisation) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST organisations/delete-lettings-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_lettings_logs_organisation_path(id: organisation) }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return LettingsLog.all + + post delete_lettings_logs_organisation_path(id: organisation, search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_lettings_logs_organisation_path(id: organisation, selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_lettings_logs_organisation_path(id: organisation, selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST organisations/delete-lettings-logs-confirmation" do + let(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:log_3) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_lettings_logs_confirmation_organisation_path(id: organisation), params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_lettings_logs_confirmation_organisation_path(id: organisation) }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_lettings_logs_organisation_path(id: organisation) + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_lettings_logs_organisation_path(id: organisation) + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :lettings, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_lettings_logs_confirmation_organisation_path(id: organisation, params:) + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE organisations/delete-lettings-logs" do + let(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + before do + delete delete_lettings_logs_organisation_path(id: organisation, params:) + end + + it "deletes the logs provided" do + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the lettings log index for that organisation and displays a notice that the logs have been deleted" do + expect(response).to redirect_to lettings_logs_organisation_path(id: organisation) + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + describe "GET organisations/delete-sales-logs" do + let!(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return SalesLog.all + + get delete_sales_logs_organisation_path(id: organisation, search:) + end + + it "displays the logs returned by the filter service" do + get delete_sales_logs_organisation_path(id: organisation) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_sales_logs_organisation_path(id: organisation) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST organisations/delete-sales-logs" do + let!(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_sales_logs_organisation_path(id: organisation) }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return SalesLog.all + + post delete_sales_logs_organisation_path(id: organisation, search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_sales_logs_organisation_path(id: organisation, selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_sales_logs_organisation_path(id: organisation, selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST organisations/delete-sales-logs-confirmation" do + let(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:log_3) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_sales_logs_confirmation_organisation_path(id: organisation), params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_sales_logs_confirmation_organisation_path(id: organisation) }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_sales_logs_organisation_path(id: organisation) + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_sales_logs_organisation_path(id: organisation) + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :sales, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_sales_logs_confirmation_organisation_path(id: organisation, params:) + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE organisations/delete-sales-logs" do + let(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + before do + delete delete_sales_logs_organisation_path(id: organisation, params:) + end + + it "deletes the logs provided" do + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the sales log index for that organisation and displays a notice that the logs have been deleted" do + expect(response).to redirect_to sales_logs_organisation_path(id: organisation) + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + end +end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 33108e465..50c4d6763 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -246,10 +246,39 @@ RSpec.describe LettingsLogsController, type: :request do end it "does not have a button for creating sales logs" do - get "/lettings-logs", headers:, params: {} + get lettings_logs_path, headers:, params: {} page.assert_selector(".govuk-button", text: "Create a new sales log", count: 0) page.assert_selector(".govuk-button", text: "Create a new lettings log", count: 1) end + + context "and the state of filters and search is such that display_delete_logs returns true" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(true) # rubocop:disable RSpec/AnyInstance + end + + it "displays the delete logs button with the correct path if there are logs visibile" do + get lettings_logs_path(search: "LC783") + expect(page).to have_link "Delete logs", href: delete_logs_lettings_logs_path(search: "LC783") + end + + it "does not display the delete logs button if there are no logs displayed" do + LettingsLog.destroy_all + get lettings_logs_path + expect(page).not_to have_link "Delete logs" + end + end + + context "and the state of filters and search is such that display_delete_logs returns false" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(false) # rubocop:disable RSpec/AnyInstance + end + + it "does not display the delete logs button even if there are logs displayed" do + get lettings_logs_path + expect(page).to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end end context "when the user is a customer support user" do @@ -347,7 +376,6 @@ RSpec.describe LettingsLogsController, type: :request do Timecop.freeze(2022, 3, 1) do example.run end - Timecop.return end let!(:lettings_log_2021) do @@ -538,7 +566,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - context "when the user is not a customer support user" do + context "when the user is a data provider" do before do sign_in user end @@ -1348,7 +1376,7 @@ RSpec.describe LettingsLogsController, type: :request do context "when user not authorised" do let(:user) { create(:user) } - it "returns 404" do + it "returns 401" do delete_request expect(response).to have_http_status(:unauthorized) end @@ -1400,7 +1428,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - describe "GET #csv-download" do + describe "GET csv-download" do let(:page) { Capybara::Node::Simple.new(response.body) } let(:user) { FactoryBot.create(:user) } let(:headers) { { "Accept" => "text/html" } } @@ -1482,7 +1510,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - describe "POST #email-csv" do + describe "POST email-csv" do let(:other_organisation) { FactoryBot.create(:organisation) } let(:user) { FactoryBot.create(:user, :support) } diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 466e7522e..a3d5ac41e 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1221,210 +1221,228 @@ RSpec.describe OrganisationsController, type: :request do end end end - end - end - - context "when the user is a support user" do - let(:user) { create(:user, :support) } - before do - allow(user).to receive(:need_two_factor_authentication?).and_return(false) - sign_in user - end - - context "when they view the lettings logs tab" do - before do - create(:lettings_log, owning_organisation: organisation) - end - - it "has CSV download buttons with the correct paths if at least 1 log exists" do - get "/organisations/#{organisation.id}/lettings-logs" - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false") - expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true") - end - - context "when you download the CSV" do - let(:other_organisation) { create(:organisation) } + context "when they view the lettings logs tab" do + let(:tenancycode) { "42" } before do - create_list(:lettings_log, 2, owning_organisation: organisation) - create(:lettings_log, owning_organisation: organisation, status: "pending", skip_update_status: true) - create_list(:lettings_log, 2, owning_organisation: other_organisation) + create(:lettings_log, owning_organisation: organisation, tenancycode:) end - it "only includes logs from that organisation" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false" + context "when there is at least one log visible" do + before do + get lettings_logs_organisation_path(organisation, search: tenancycode) + end + + it "shows the delete logs button with the correct path" do + expect(page).to have_link "Delete logs", href: delete_lettings_logs_organisation_path(search: tenancycode) + end - expect(page).to have_text("You've selected 3 logs.") + it "has CSV download buttons with the correct paths" do + expect(page).to have_link "Download (CSV)", href: lettings_logs_csv_download_organisation_path(organisation, codes_only: false, search: tenancycode) + expect(page).to have_link "Download (CSV, codes only)", href: lettings_logs_csv_download_organisation_path(organisation, codes_only: true, search: tenancycode) + end end - it "provides the organisation to the mail job" do - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false) - end + context "when there are no visible logs" do + before do + LettingsLog.destroy_all + get lettings_logs_organisation_path(organisation) + end - it "provides the export type to the mail job" do - codes_only_export_type = false - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) - codes_only_export_type = true - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + it "does not show the delete logs button " do + expect(page).not_to have_link "Delete logs" + end + + it "does not show the csv download buttons" do + expect(page).not_to have_link "Download (CSV)" + expect(page).not_to have_link "Download (CSV, codes only)" + end end - end - end - context "when they view the sales logs tab" do - before do - create(:sales_log, owning_organisation: organisation) - end + context "when you download the CSV" do + let(:other_organisation) { create(:organisation) } - it "has CSV download buttons with the correct paths if at least 1 log exists" do - get "/organisations/#{organisation.id}/sales-logs" - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false") - expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true") - end + before do + create_list(:lettings_log, 2, owning_organisation: organisation) + create(:lettings_log, owning_organisation: organisation, status: "pending", skip_update_status: true) + create_list(:lettings_log, 2, owning_organisation: other_organisation) + end - context "when you download the CSV" do - let(:other_organisation) { create(:organisation) } + it "only includes logs from that organisation" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false" - before do - create_list(:sales_log, 2, owning_organisation: organisation) - create(:sales_log, owning_organisation: organisation, status: "pending", skip_update_status: true) - create_list(:sales_log, 2, owning_organisation: other_organisation) - end + expect(page).to have_text("You've selected 3 logs.") + end - it "only includes logs from that organisation" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false" + it "provides the organisation to the mail job" do + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false) + end - expect(page).to have_text("You've selected 3 logs.") + it "provides the export type to the mail job" do + codes_only_export_type = false + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + codes_only_export_type = true + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + end end + end - it "provides the organisation to the mail job" do - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, "sales") + context "when they view the sales logs tab" do + before do + create(:sales_log, owning_organisation: organisation) end - it "provides the log type to the mail job" do - log_type = "sales" - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, log_type) + it "has CSV download buttons with the correct paths if at least 1 log exists" do + get "/organisations/#{organisation.id}/sales-logs" + expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false") + expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true") end - it "provides the export type to the mail job" do - codes_only_export_type = false - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") - codes_only_export_type = true - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") - end - end - end + context "when you download the CSV" do + let(:other_organisation) { create(:organisation) } - describe "GET #download_lettings_csv" do - it "renders a page with the correct header" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} - header = page.find_css("h1") - expect(header.text).to include("Download CSV") - end + before do + create_list(:sales_log, 2, owning_organisation: organisation) + create(:sales_log, owning_organisation: organisation, status: "pending", skip_update_status: true) + create_list(:sales_log, 2, owning_organisation: other_organisation) + end - it "renders a form with the correct target containing a button with the correct text" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} - form = page.find("form.button_to") - expect(form[:method]).to eq("post") - expect(form[:action]).to eq("/organisations/#{organisation.id}/lettings-logs/email-csv") - expect(form).to have_button("Send email") - end + it "only includes logs from that organisation" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false" - it "when codes_only query parameter is false, form contains hidden field with correct value" do - codes_only = false - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + expect(page).to have_text("You've selected 3 logs.") + end - it "when codes_only query parameter is true, form contains hidden field with correct value" do - codes_only = true - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "provides the organisation to the mail job" do + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, "sales") + end - it "when query string contains search parameter, form contains hidden field with correct value" do - search_term = "blam" - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("search", type: "hidden") - expect(hidden_field.value).to eq(search_term) - end - end + it "provides the log type to the mail job" do + log_type = "sales" + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, log_type) + end - describe "GET #download_sales_csv" do - it "renders a page with the correct header" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} - header = page.find_css("h1") - expect(header.text).to include("Download CSV") + it "provides the export type to the mail job" do + codes_only_export_type = false + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") + codes_only_export_type = true + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") + end + end end - it "renders a form with the correct target containing a button with the correct text" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} - form = page.find("form.button_to") - expect(form[:method]).to eq("post") - expect(form[:action]).to eq("/organisations/#{organisation.id}/sales-logs/email-csv") - expect(form).to have_button("Send email") - end + describe "GET #download_lettings_csv" do + it "renders a page with the correct header" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} + header = page.find_css("h1") + expect(header.text).to include("Download CSV") + end - it "when codes_only query parameter is false, form contains hidden field with correct value" do - codes_only = false - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "renders a form with the correct target containing a button with the correct text" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} + form = page.find("form.button_to") + expect(form[:method]).to eq("post") + expect(form[:action]).to eq("/organisations/#{organisation.id}/lettings-logs/email-csv") + expect(form).to have_button("Send email") + end - it "when codes_only query parameter is true, form contains hidden field with correct value" do - codes_only = true - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "when codes_only query parameter is false, form contains hidden field with correct value" do + codes_only = false + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end - it "when query string contains search parameter, form contains hidden field with correct value" do - search_term = "blam" - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("search", type: "hidden") - expect(hidden_field.value).to eq(search_term) - end - end + it "when codes_only query parameter is true, form contains hidden field with correct value" do + codes_only = true + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end - context "when they view the users tab" do - before do - get "/organisations/#{organisation.id}/users" + it "when query string contains search parameter, form contains hidden field with correct value" do + search_term = "blam" + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("search", type: "hidden") + expect(hidden_field.value).to eq(search_term) + end end - it "has a CSV download button with the correct path" do - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/users.csv") - end + describe "GET #download_sales_csv" do + it "renders a page with the correct header" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} + header = page.find_css("h1") + expect(header.text).to include("Download CSV") + end - context "when you download the CSV" do - let(:headers) { { "Accept" => "text/csv" } } - let(:other_organisation) { create(:organisation) } + it "renders a form with the correct target containing a button with the correct text" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} + form = page.find("form.button_to") + expect(form[:method]).to eq("post") + expect(form[:action]).to eq("/organisations/#{organisation.id}/sales-logs/email-csv") + expect(form).to have_button("Send email") + end + + it "when codes_only query parameter is false, form contains hidden field with correct value" do + codes_only = false + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end + + it "when codes_only query parameter is true, form contains hidden field with correct value" do + codes_only = true + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end + it "when query string contains search parameter, form contains hidden field with correct value" do + search_term = "blam" + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("search", type: "hidden") + expect(hidden_field.value).to eq(search_term) + end + end + + context "when they view the users tab" do before do - create_list(:user, 3, organisation:) - create_list(:user, 2, organisation: other_organisation) + get "/organisations/#{organisation.id}/users" + end + + it "has a CSV download button with the correct path" do + expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/users.csv") end - it "only includes users from that organisation" do - get "/organisations/#{other_organisation.id}/users", headers:, params: {} - csv = CSV.parse(response.body) - expect(csv.count).to eq(other_organisation.users.count + 1) + context "when you download the CSV" do + let(:headers) { { "Accept" => "text/csv" } } + let(:other_organisation) { create(:organisation) } + + before do + create_list(:user, 3, organisation:) + create_list(:user, 2, organisation: other_organisation) + end + + it "only includes users from that organisation" do + get "/organisations/#{other_organisation.id}/users", headers:, params: {} + csv = CSV.parse(response.body) + expect(csv.count).to eq(other_organisation.users.count + 1) + end end end end diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index 30d756356..ff9130e75 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -112,9 +112,11 @@ RSpec.describe SalesLogsController, type: :request do let(:user) { FactoryBot.create(:user) } let(:organisation) { user.organisation } let(:other_organisation) { FactoryBot.create(:organisation) } + let(:purchaser_code) { "coop123" } let!(:sales_log) do FactoryBot.create( :sales_log, + purchid: purchaser_code, owning_organisation: organisation, ) end @@ -175,6 +177,36 @@ RSpec.describe SalesLogsController, type: :request do end end + context "and the state of filters and search is such that display_delete_logs returns true" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(true) # rubocop:disable RSpec/AnyInstance + end + + it "displays the delete logs button with the correct path if there are logs visibile" do + get sales_logs_path(search: purchaser_code) + expect(page).to have_link "Delete logs", href: delete_logs_sales_logs_path(search: purchaser_code) + end + + it "does not display the delete logs button if there are no logs displayed" do + SalesLog.destroy_all + get sales_logs_path(search: "gibberish_e9o87tvbyc4875g") + expect(page).not_to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end + + context "and the state of filters and search is such that display_delete_logs returns false" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(false) # rubocop:disable RSpec/AnyInstance + end + + it "does not display the delete logs button even if there are logs displayed" do + get sales_logs_path + expect(page).to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end + context "when there is a pending log" do let!(:invisible_log) do FactoryBot.create( diff --git a/spec/views/form/page_view_spec.rb b/spec/views/form/page_view_spec.rb index 074a7fd6a..614f56562 100644 --- a/spec/views/form/page_view_spec.rb +++ b/spec/views/form/page_view_spec.rb @@ -22,8 +22,6 @@ RSpec.describe "form/page" do Singleton.__init__(FormHandler) example.run end - Timecop.return - Singleton.__init__(FormHandler) end before do @@ -45,7 +43,7 @@ RSpec.describe "form/page" do context "with a page containing a description" do let(:description) { "Test description with link." } let(:page_attributes) { { description: } } - let(:expected_html) { '

Test description with link.

' } + let(:expected_html) { "

#{description}

" } it "renders the description" do expect(rendered).to match(expected_html) diff --git a/spec/views/logs/delete_logs_spec.rb b/spec/views/logs/delete_logs_spec.rb new file mode 100644 index 000000000..6dcb8c423 --- /dev/null +++ b/spec/views/logs/delete_logs_spec.rb @@ -0,0 +1,121 @@ +require "rails_helper" + +RSpec.describe "logs/delete_logs.html.erb" do + let(:user) { create(:user, :support, name: "Dirk Gently") } + let(:lettings_log_1) { create(:lettings_log, tenancycode: "Holistic", propcode: "Detective Agency", created_by: user) } + let(:lettings_logs) { [lettings_log_1] } + let(:paths) do + { + delete_confirmation_path: delete_logs_confirmation_lettings_logs_path, + back_to_logs_path: lettings_logs_path(search: "search_term"), + delete_path: delete_logs_lettings_logs_path, + } + end + let(:delete_logs_form) { Forms::DeleteLogsForm.new(log_type: :lettings, current_user: user, **paths) } + + before do + sign_in user + allow(FilterManager).to receive(:filter_logs).and_return lettings_logs + assign(:delete_logs_form, delete_logs_form) + end + + it "has the correct h1 content" do + render + fragment = Capybara::Node::Simple.new(rendered) + h1 = fragment.find("h1") + expect(h1.text).to include "Review the logs you want to delete" + end + + context "when there is one log to delete" do + it "shows the informative text in the singular" do + render + fragment = Capybara::Node::Simple.new(rendered) + info_text = fragment.first("p").text + expect(info_text).to eq "You've selected 1 log to delete" + end + end + + context "when there is more than one log to delete" do + let(:lettings_log_2) { create(:lettings_log, tenancycode: "01-354", propcode: "9112") } + + before do + lettings_logs << lettings_log_2 + allow(FilterManager).to receive(:filter_logs).and_return lettings_logs + delete_logs_form = Forms::DeleteLogsForm.new(log_type: :lettings, current_user: user, **paths) + assign(:delete_logs_form, delete_logs_form) + end + + it "shows the informative text in the plural" do + render + fragment = Capybara::Node::Simple.new(rendered) + info_text = fragment.first("p").text + expect(info_text).to eq "You've selected #{lettings_logs.count} logs to delete" + end + end + + context "when the table contains lettings logs" do + it "shows the correct headers in the table" do + render + fragment = Capybara::Node::Simple.new(rendered) + headers = fragment.find_all("table thead tr th").map(&:text) + expect(headers).to eq ["Log ID", "Tenancy code", "Property reference", "Status", "Delete?"] + end + + it "shows the correct information in each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + row_data = fragment.find_all("table tbody tr td").map(&:text)[0...-1].map(&:strip) + expect(row_data).to eq [lettings_log_1.id.to_s, lettings_log_1.tenancycode, lettings_log_1.propcode, lettings_log_1.status.humanize.capitalize] + end + + it "links to the relevant log on each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + first_cell = fragment.first("table tbody tr td") + expect(first_cell).to have_link lettings_log_1.id.to_s, href: lettings_log_path(lettings_log_1) + end + end + + context "when the table contains sales logs" do + let(:sales_log) { create(:sales_log, purchid: "Interconnectedness", saledate: Time.zone.today, created_by: user) } + let(:sales_logs) { [sales_log] } + let(:delete_logs_form_sales) { Forms::DeleteLogsForm.new(log_type: :sales, current_user: user, **paths) } + + before do + sign_in user + allow(FilterManager).to receive(:filter_logs).and_return sales_logs + assign(:delete_logs_form, delete_logs_form_sales) + end + + it "shows the correct headers in the table" do + render + fragment = Capybara::Node::Simple.new(rendered) + headers = fragment.find_all("table thead tr th").map(&:text) + expect(headers).to eq ["Log ID", "Purchaser code", "Sale completion date", "Status", "Delete?"] + end + + it "shows the correct information in each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + row_data = fragment.find_all("table tbody tr td").map(&:text)[0...-1].map(&:strip) + expect(row_data).to eq [sales_log.id.to_s, sales_log.purchid, sales_log.saledate.to_formatted_s(:govuk_date), sales_log.status.humanize.capitalize] + end + + it "links to the relevant log on each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + first_cell = fragment.first("table tbody tr td") + expect(first_cell).to have_link sales_log.id.to_s, href: sales_log_path(sales_log) + end + end + + it "shows a checkbox with the correct hidden label in the final cell of each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + final_cell = fragment.find_all("table tbody tr td")[-1] + expect(final_cell.find("input")[:type]).to eq "checkbox" + checkbox_label = final_cell.find("label span") + expect(checkbox_label.text).to eq lettings_log_1.id.to_s + expect(checkbox_label[:class]).to include "govuk-visually-hidden" + end +end