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