Browse Source

CLDC-2290 implement delete multiple logs story (#1657)

* add a button to the logs list to delete multiple logs
style and position of button
helpers for displaying the button conditionally depending on user role and what filters and search are active

* correct indentation from 4 spaces to 2 in view file

* test appearance of delete logs button on index page for lettings logs

* write a happy path feature test for the entire journey

* create basic tests for the view component for listing logs to delete

* create request tests for the GET delete-logs path

* create request tests for the GET delete-logs-confirmation path

* create request tests for the DELETE delete-logs path

* comprehensive reworking after code review
ensure that we are not passing lists of ids through params in the query string, risking overflowing the maximum URL length,
adjust tests accordingly,
do not attempt to reuse the same table for sales and lettings

* alter config to allow creating controllers from the command line with associated spec files that matches how we test

* extract controller methods and associated tests to do with the delete logs feature into their own controller,
    amend routes accordingly

* implement same work for sales as for lettings

* implement the story for lettings and sales logs under the organisation tab
routing and controller methods
testing for deleting sales logs, lettings or sales logs for an organisation
move storage of relevant routes inside the form object as a comprehensive view model

* merge the delete pages for lettings logs and sales logs, add to the tests for the lettings page to test sales specific content

* minor refactor to delete logs controller: ensure session filters are only fetched from teh session when needed and extract discard logs method to private method

* extract tables for lettings and sales to own partials

* refactor delete logs controller after tech review
improve the private method that builds the form object so that it has the flexibility to do so for all controller methods
ensure that the search term is passed to the delete logs controller when navigating through the organisations tab
ensure that noly logs for that organisation are displayed when navigating to delete logs through the organisations tab

* remove unnecessary untested arguments

* test new helper methods

* implement dirty fiddle to get the checkboxes smaller and also not misaligned

* ensure delete logs button is always visible on log lists when in the organisations tab

* minor linting corrections

* revert change, causing errors and outside the scope of this ticket

* simplify tests for whether delete logs button appears on index page

* replicate request specs from lettings for sales and organisations controllers

* minor refactor of lettings log feature spec setup, replicate happy path for sales

* minor refactors after rebasing onto Nat's work

* temp

* write tests for the delete logs form object

* lint: add new line at end of file

* respond to PO feedback
the log id in the delte logs table should be a link to the log
the delete logs button should be visible when the user is in a bulk upload journey
updated associated tests
pull/1705/head
Arthur Campbell 2 years ago committed by GitHub
parent
commit
cec1208b35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 196
      app/controllers/delete_logs_controller.rb
  2. 2
      app/controllers/lettings_logs_controller.rb
  3. 4
      app/controllers/organisations_controller.rb
  4. 1
      app/controllers/sales_logs_controller.rb
  5. 5
      app/frontend/styles/_delete-logs-table.scss
  6. 3
      app/frontend/styles/application.scss
  7. 12
      app/helpers/filters_helper.rb
  8. 13
      app/helpers/log_list_helper.rb
  9. 44
      app/models/forms/delete_logs_form.rb
  10. 30
      app/views/logs/_delete_logs_table_lettings.html.erb
  11. 30
      app/views/logs/_delete_logs_table_sales.html.erb
  12. 12
      app/views/logs/_log_list.html.erb
  13. 22
      app/views/logs/delete_logs.html.erb
  14. 25
      app/views/logs/delete_logs_confirmation.html.erb
  15. 6
      app/views/logs/index.html.erb
  16. 3
      app/views/logs/update_logs.html.erb
  17. 2
      app/views/organisations/logs.html.erb
  18. 9
      config/application.rb
  19. 5
      config/locales/en.yml
  20. 18
      config/routes.rb
  21. 40
      spec/features/lettings_log_spec.rb
  22. 73
      spec/features/sales_log_spec.rb
  23. 78
      spec/helpers/filters_helper_spec.rb
  24. 72
      spec/helpers/log_list_helper_spec.rb
  25. 89
      spec/models/forms/delete_logs_form_spec.rb
  26. 946
      spec/requests/delete_logs_controller_spec.rb
  27. 40
      spec/requests/lettings_logs_controller_spec.rb
  28. 40
      spec/requests/organisations_controller_spec.rb
  29. 32
      spec/requests/sales_logs_controller_spec.rb
  30. 4
      spec/views/form/page_view_spec.rb
  31. 121
      spec/views/logs/delete_logs_spec.rb

196
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

2
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

4
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

1
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

5
app/frontend/styles/_delete-logs-table.scss

@ -0,0 +1,5 @@
.checkbox-cell {
.govuk-checkboxes__item {
margin-top: -7px;
}
}

3
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 {

12
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",

13
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

44
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

30
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 %>

30
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 %>

12
app/views/logs/_log_list.html.erb

@ -1,11 +1,21 @@
<h2 class="govuk-body">
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters">
<%= 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 %>
</div>
<div class="govuk-grid-column-one-quarter govuk-!-text-align-right">
<% 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 %>
</div>
</div>
</h2>
<% logs.map do |log| %>
<%= render(LogSummaryComponent.new(current_user:, log:)) %>

22
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 %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= title %></span>
Review the logs you want to delete
</h1>
<p>You've selected <%= @delete_logs_form.log_count %> <%= "log".pluralize(@delete_logs_form.log_count) %> to delete</p>
<div class="govuk-checkboxes govuk-checkboxes--small">
<%= 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 %>
</div>

25
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 %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= title %></span>
Are you sure you want to delete these logs?
</h1>
<% log_count = @delete_logs_form.selected_ids.count %>
<p>You've selected <%= log_count %> <%= "log".pluralize(log_count) %> to delete</p>
<%= govuk_warning_text(icon_fallback_text: "Danger") do %>
You will not be able to undo this action
<% end %>
<div class="govuk-button-group">
<%= 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 %>
</div>

6
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" } %>
</div>

3
app/views/logs/update_logs.html.erb

@ -13,7 +13,6 @@
</div>
<% 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| %>
@ -35,7 +34,7 @@
scope: "row",
class: "govuk-!-text-align-right",
}) do %>
<%= govuk_link_to("Update now", send(log.form.unresolved_log_path, log)) %>
<%= govuk_link_to "Update now", send(log.form.unresolved_log_path, log) %>
<% end %>
<% end %>
<% end %>

2
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" } %>
</div>

9
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

5
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.

18
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

40
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

73
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

78
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

72
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

89
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

946
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

40
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) }

40
spec/requests/organisations_controller_spec.rb

@ -1221,26 +1221,43 @@ RSpec.describe OrganisationsController, type: :request do
end
end
end
context "when they view the lettings logs tab" do
let(:tenancycode) { "42" }
before do
create(:lettings_log, owning_organisation: organisation, tenancycode:)
end
context "when there is at least one log visible" do
before do
get lettings_logs_organisation_path(organisation, search: tenancycode)
end
context "when the user is a support user" do
let(:user) { create(:user, :support) }
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
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
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
context "when they view the lettings logs tab" do
context "when there are no visible logs" do
before do
create(:lettings_log, owning_organisation: organisation)
LettingsLog.destroy_all
get lettings_logs_organisation_path(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")
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
context "when you download the CSV" do
@ -1429,6 +1446,7 @@ RSpec.describe OrganisationsController, type: :request do
end
end
end
end
describe "GET #data_sharing_agreement" do
context "when not signed in" do

32
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(

4
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 <a class=\"govuk-link\" href=\"/test-link\">with link</a>." }
let(:page_attributes) { { description: } }
let(:expected_html) { '<p class="govuk-body govuk-body-m">Test description <a class="govuk-link" href="/test-link">with link</a>.</p>' }
let(:expected_html) { "<p class=\"govuk-body govuk-body-m\">#{description}</p>" }
it "renders the description" do
expect(rendered).to match(expected_html)

121
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
Loading…
Cancel
Save