diff --git a/app/views/conservation_records/_cost_return_form.html.erb b/app/views/conservation_records/_cost_return_form.html.erb new file mode 100644 index 000000000..facee640a --- /dev/null +++ b/app/views/conservation_records/_cost_return_form.html.erb @@ -0,0 +1,55 @@ +<%# expects local `conservation_record` %> +
+
+

Cost and Return Information

+
+ + <%= form_with model: [conservation_record, conservation_record.cost_return_report], + class: 'disable_input', + local: true do |f| %> +

+ <%= f.label :shipping_cost, 'Shipping To Vendor Cost' %>
+ <%= f.number_field :shipping_cost, + value: (number_with_precision(f.object.shipping_cost, precision: 2) || 0), + step: 0.01, + class: 'form-control' %> +

+

+ <%= f.label :repair_estimate, 'Repair Cost Estimate' %>
+ <%= f.number_field :repair_estimate, + value: (number_with_precision(f.object.repair_estimate, precision: 2) || 0), + step: 0.01, + class: 'form-control' %> +

+

+ <%= f.label :repair_cost, 'Actual Billed Repair Cost' %>
+ <%= f.number_field :repair_cost, + value: (number_with_precision(f.object.repair_cost, precision: 2) || 0), + step: 0.01, + class: 'form-control' %> +

+

+ <%= f.label :invoice_sent_to_business_office, + 'Date Invoice Sent to Business Office' %>
+ <%= f.date_field :invoice_sent_to_business_office, + class: 'form-control' %> +

+

+ <%= f.check_box :complete %> + <%= f.label :complete, 'Complete (returned to origin)' %> +

+

+ <%= f.label :returned_to_origin, 'Date Returned to Origin' %>
+ <%= f.date_field :returned_to_origin, + class: 'form-control' %> +

+

+ <%= f.label :note, 'Note' %>
+ <%= f.text_area :note, class: 'form-control' %> +

+

+ <%= f.submit 'Save Cost and Return Information', + class: 'btn btn-primary' %> +

+ <% end %> +
diff --git a/app/views/conservation_records/_details_table.html.erb b/app/views/conservation_records/_details_table.html.erb new file mode 100644 index 000000000..bde2e91df --- /dev/null +++ b/app/views/conservation_records/_details_table.html.erb @@ -0,0 +1,26 @@ +<%# expects local `conservation_record` %> + + + <% fields = [ + ['Database ID', :id], + ['Date Received', ->(r) { r.date_received_in_preservation_services&.strftime('%m/%d/%Y') }], + ['Department', ->(r) { controlled_vocabulary_lookup(r.department) }], + ['Title', :title], + ['Author', :author], + ['Imprint', :imprint], + ['Call Number', :call_number], + ['Item Record Number', :item_record_number], + ['Is Digitized?', :digitization] + ] %> + + <% fields.each do |label, extractor| %> + + + + + <% end %> + +
<%= label %> + <% value = extractor.is_a?(Symbol) ? conservation_record.public_send(extractor) : extractor.call(conservation_record) %> + <%= value %> +
diff --git a/app/views/conservation_records/_repair_section.html.erb b/app/views/conservation_records/_repair_section.html.erb new file mode 100644 index 000000000..f3978776a --- /dev/null +++ b/app/views/conservation_records/_repair_section.html.erb @@ -0,0 +1,52 @@ +<%# locals: + type, # e.g. "InHouseRepairs" + title, # e.g. "In-House Repairs" + klass, # e.g. InHouseRepairRecord + collection, # e.g. @in_house_repairs + modal_id, # e.g. "inHouseRepairModal" + generator_method # OPTIONAL: exact helper name, e.g. "generate_con_tech_string" +%> + +<%# Derive a default helper if none was passed %> +<% default_gen = "generate_#{type.singularize.underscore}_string" %> +<% gen = local_assigns[:generator_method] || default_gen %> +<% btn_label = local_assigns[:button_label] || title.singularize %> + +
+

<%= title %>

+ <% if can? :crud, klass %> + + <% end %> +
+ +<% if collection.empty? %> +
+ There are no <%= title.downcase %> to show. +
+<% else %> + +<% end %> diff --git a/app/views/conservation_records/_tab_content.html.erb b/app/views/conservation_records/_tab_content.html.erb new file mode 100644 index 000000000..fc48fcadc --- /dev/null +++ b/app/views/conservation_records/_tab_content.html.erb @@ -0,0 +1,12 @@ +<%# locals: id_prefix, tabs, content_partials, plus any others like conservation_record %> +
+ <% tabs.keys.each_with_index do |tab_id, idx| %> +
"> + <%= render partial: content_partials[idx], + locals: { conservation_record: local_assigns[:conservation_record] } %> +
+ <% end %> +
diff --git a/app/views/conservation_records/_tabs.html.erb b/app/views/conservation_records/_tabs.html.erb new file mode 100644 index 000000000..03f5dcfe3 --- /dev/null +++ b/app/views/conservation_records/_tabs.html.erb @@ -0,0 +1,16 @@ +<%# locals: type, id_prefix, tabs (hash of id => label) %> + diff --git a/app/views/conservation_records/modals/_conservators_technicians_modal.html.erb b/app/views/conservation_records/modals/_conservators_technicians_modal.html.erb new file mode 100644 index 000000000..20924df5d --- /dev/null +++ b/app/views/conservation_records/modals/_conservators_technicians_modal.html.erb @@ -0,0 +1,50 @@ + diff --git a/app/views/conservation_records/modals/_external_repair_modal.html.erb b/app/views/conservation_records/modals/_external_repair_modal.html.erb new file mode 100644 index 000000000..103f450a8 --- /dev/null +++ b/app/views/conservation_records/modals/_external_repair_modal.html.erb @@ -0,0 +1,72 @@ + diff --git a/app/views/conservation_records/modals/_in_house_repair_modal.html.erb b/app/views/conservation_records/modals/_in_house_repair_modal.html.erb new file mode 100644 index 000000000..8f1b8815e --- /dev/null +++ b/app/views/conservation_records/modals/_in_house_repair_modal.html.erb @@ -0,0 +1,83 @@ + diff --git a/app/views/conservation_records/show.html.erb b/app/views/conservation_records/show.html.erb index 3b07dae5a..02d790ad1 100644 --- a/app/views/conservation_records/show.html.erb +++ b/app/views/conservation_records/show.html.erb @@ -1,329 +1,86 @@ -<%= link_to '← Return to List', conservation_records_path %> -
-

<%= @conservation_record.title %>

- <% if can? :crud, ConservationRecord %> - <%= link_to 'Edit Conservation Record', edit_conservation_record_path(@conservation_record), class: "btn btn-primary" %> - <% end %> -
- -<%= link_to 'Download Conservation Worksheet', conservation_worksheet_conservation_record_path(@conservation_record), target: "_blank" %> - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Database ID<%= @conservation_record.id %>
Date Received - <% unless @conservation_record.date_received_in_preservation_services.nil? %> - <%= @conservation_record.date_received_in_preservation_services.strftime("%m/%d/%Y") %> - <% end %> -
Department<%= controlled_vocabulary_lookup(@conservation_record.department) %>
Title<%= @conservation_record.title %>
Author<%= @conservation_record.author %>
Imprint<%= @conservation_record.imprint %>
Call Number<%= @conservation_record.call_number %>
Item Record Number<%= @conservation_record.item_record_number %>
Is Digitized?<%= @conservation_record.digitization %>
-

- -
-

In-House Repairs

- <% if can? :crud, InHouseRepairRecord %> - - <% end %> -
- <% if @in_house_repairs.count == 0 %> -
-
- There are no in-house repairs to show. -
-
- <% else %> - - <% end %> +
+ <%= link_to '← Return to List', conservation_records_path %> -
-
-

External Repairs

- <% if can? :crud, ExternalRepairRecord %> - - <% end %> -
- <% if @external_repairs.count == 0 %> -
-
- There are no external repairs to show. -
-
- <% else %> -
    - <% @external_repairs.each_with_index do |repair, i| %> -
  • - <%= generate_external_repair_string(repair, i) %> - - <% if can? :crud, ExternalRepairRecord %> - <%= link_to [repair.conservation_record, repair], id: "delete_external_repair_record_#{repair.id}", method: :delete, data: {confirm: "Are you sure?"} do %> - - <% end %> - <% end %> -
  • - <% end %> -
- <% end %> -
-
-

Conservators and Technicians

- <% if can? :crud, ConTechRecord %> - - <% end %> -
- <% if @con_tech_records.count == 0 %> -
-
- There are no Conservators and Technicians to show. -
-
- <% else %> -
    - <% @con_tech_records.each_with_index do |record, i| %> -
  • - <%= generate_con_tech_string(record, i) %> - <% if can? :crud, ConTechRecord %> - <%= link_to [record.conservation_record, record], method: :delete do %> - - <% end %> - <% end %> -
  • - <% end %> -
+
+

<%= @conservation_record.title %>

+ <% if can? :crud, ConservationRecord %> + <%= link_to 'Edit Conservation Record', + edit_conservation_record_path(@conservation_record), + class: 'btn btn-primary' %> <% end %> -
-
- -
- - - -
-
<%= render 'treatment_reports/form' %>
-
<%= render 'treatment_reports/abbreviated_treatment_report_form' %>
-
- <%= link_to 'Download Abbreviated Treatment Report', abbreviated_treatment_report_conservation_record_path(@conservation_record), target: '_blank' %> -
+ <%= link_to 'Download Conservation Worksheet', + conservation_worksheet_conservation_record_path(@conservation_record), + target: '_blank' %> + + <%= render 'conservation_records/tabs', + type: 'nav nav-tabs', + id_prefix: 'myTab', + tabs: { 'home' => 'Item Detail' } %> + + <%= render 'conservation_records/tab_content', + id_prefix: 'myTab', + tabs: { 'home' => 'Item Detail' }, + content_partials: ['conservation_records/details_table'], + conservation_record: @conservation_record %> + + <%= render 'conservation_records/repair_section', + type: 'InHouseRepairs', + title: 'In-House Repairs', + klass: InHouseRepairRecord, + collection: @in_house_repairs, + modal_id: 'inHouseRepairModal' %> + + <%= render 'conservation_records/repair_section', + type: 'ExternalRepairs', + title: 'External Repairs', + klass: ExternalRepairRecord, + collection: @external_repairs, + modal_id: 'externalRepairModal' %> + + <%= render 'conservation_records/repair_section', + type: 'ConTechRecords', + title: 'Conservators and Technicians', + klass: ConTechRecord, + collection: @con_tech_records, + modal_id: 'ConservatorsTechniciansModal', + generator_method: 'generate_con_tech_string', + button_label: 'Conservators and Technicians' %> +
+ <%= render 'conservation_records/tabs', + type: 'nav nav-pills', + id_prefix: 'reportTab', + tabs: { + 'treatment-report' => 'Treatment Report', + 'abbreviated-treatment-report' => 'Abbreviated Treatment Report' + } %> + + <%= render 'conservation_records/tab_content', + id_prefix: 'reportTab', + tabs: { + 'treatment-report' => 'Treatment Report', + 'abbreviated-treatment-report' => 'Abbreviated Treatment Report' + }, + content_partials: %w[treatment_reports/form treatment_reports/abbreviated_treatment_report_form] %> -
-

Cost and Return Information

+ <%= link_to 'Download Abbreviated Treatment Report', + abbreviated_treatment_report_conservation_record_path(@conservation_record), + target: '_blank' %>
- <%= form_with(model: [@conservation_record, @conservation_record.cost_return_report]) do |f| %> - <% unless can?(:update, @conservation_record) %> -
- <% end %> -

- <%= f.label :shipping_cost, "Shipping To Vendor Cost" %> - <%= f.number_field :shipping_cost, :value => (number_with_precision(f.object.shipping_cost, :precision => 2) || 0), step: 0.01, class: 'form-control' %> -

- -

- <%= f.label :repair_estimate, "Repair Cost Estimate" %> - <%= f.number_field :repair_estimate, :value => (number_with_precision(f.object.repair_estimate, :precision => 2) || 0), step: 0.01, class: 'form-control' %> -

- -

- <%= f.label :repair_cost, "Actual Billed Repair Cost" %> - <%= f.number_field :repair_cost, :value => (number_with_precision(f.object.repair_cost, :precision => 2) || 0), step: 0.01, class: 'form-control' %> -

- -

- <%= f.label :invoice_sent_to_business_office, "Date Invoice Sent to Business Office" %> - <%= f.date_field(:invoice_sent_to_business_office, class: 'form-control') %> -

+
-

- <%= f.check_box(:complete) %> - <%= f.label(:complete, "Complete (returned to origin)") %> -

+ <%= render 'conservation_records/cost_return_form', + conservation_record: @conservation_record %> -

- <%= f.label :returned_to_origin, "Date Returned to Origin" %> - <%= f.date_field(:returned_to_origin, class: 'form-control') %> -

+ <%= render 'conservation_records/modals/in_house_repair_modal' %> + <%= render 'conservation_records/modals/external_repair_modal' %> + <%= render 'conservation_records/modals/conservators_technicians_modal' %> -

- <%= f.label :note, "Note" %> - <%= f.text_area :note, class: 'form-control' %> -

- -

- <%= f.submit 'Save Cost and Return Information', class: 'btn btn-primary' %> -

- - <% unless can?(:update, @conservation_record) %> -
- <% end %> + <% unless can? :crud, ConservationRecord %> + <% end %>
-
- - - - - - diff --git a/public/.well-known/appspecific/com.chrome.devtools.json b/public/.well-known/appspecific/com.chrome.devtools.json new file mode 100644 index 000000000..e69de29bb diff --git a/spec/features/admin_user_spec.rb b/spec/features/admin_user_spec.rb index 4ec647e89..790bb644d 100644 --- a/spec/features/admin_user_spec.rb +++ b/spec/features/admin_user_spec.rb @@ -126,8 +126,8 @@ visit conservation_records_path click_link(conservation_record.title, match: :prefer_exact) - expect(page).to have_button('Add In-House Repairs') - click_button('Add In-House Repairs') + expect(page).to have_button('Add In-House Repair') + click_button('Add In-House Repair') select('Haritha Vytla', from: 'in_house_performed_by_user_id', match: :first) select('Mend paper', from: 'in_house_repair_type', match: :first) fill_in 'in_house_minutes_spent', with: '10' diff --git a/spec/features/end_to_end_spec.rb b/spec/features/end_to_end_spec.rb index 02f00b404..0241359b8 100644 --- a/spec/features/end_to_end_spec.rb +++ b/spec/features/end_to_end_spec.rb @@ -42,7 +42,7 @@ expect(page).to have_content('Cost and Return Information') expect(page).to have_button('Save Cost and Return Information', disabled: true) expect(page).to have_no_link('Edit Conservation Record') - expect(page).to have_no_button('Add In-House Repairs') + expect(page).to have_no_button('Add In-House Repair') expect(page).to have_no_button('Add External Repair') expect(page).to have_no_button('Add Conservators and Technicians') expect(page).to have_button('Save Treatment Report', disabled: true) @@ -106,8 +106,8 @@ # In_House Repair visit conservation_records_path click_link(conservation_record.title, match: :prefer_exact) - expect(page).to have_button('Add In-House Repairs') - click_button('Add In-House Repairs') + expect(page).to have_button('Add In-House Repair') + click_button('Add In-House Repair') expect(page).to have_button('Create In-House Repair Record') select('Chuck Greenman', from: 'in_house_performed_by_user_id', match: :first) select('Soft slipcase', from: 'in_house_repair_type', match: :first) @@ -210,7 +210,7 @@ let!(:staff_code) { create(:staff_code, code: 'test', points: 10) } let(:today_date) { Time.zone.today.strftime('%Y-%m-%d') } - it 'allows User to login and show Conservation Records' do + it 'allows User to login and show Conservation Records', aggregate_failures: true do # Login visit dev_login_path expect(page).to have_text('Please sign in with your UC', wait: 10) @@ -309,8 +309,8 @@ # Create In-House Repair visit conservation_records_path find('a', text: conservation_record.title, match: :prefer_exact, wait: 10).click - expect(page).to have_button('Add In-House Repairs', wait: 10) - find('button', text: 'Add In-House Repairs', wait: 10).click + expect(page).to have_button('Add In-House Repair', wait: 10) + find('button', text: 'Add In-House Repair', wait: 10).click select 'Haritha Vytla', from: 'in_house_performed_by_user_id', match: :first repair_types = find('#in_house_repair_type', wait: 10).all('option').collect(&:text) expect(repair_types[1..]).to start_with('updated_key_string') @@ -322,18 +322,27 @@ expect(page).to have_text('Mend paper performed by Haritha Vytla in 2 minutes. Other note: Some Other note for the in-house repair', wait: 10) # Delete In-House Repair - expect(page).to have_selector("a[id='delete_in_house_repair_record_1']", visible: true, wait: 10) - find("a[id='delete_in_house_repair_record_1']", visible: true, wait: 10) + repair = InHouseRepairRecord.last + record_id = page.current_path.split('/')[2] + delete_path = "/conservation_records/#{record_id}/in_house_repair_records/#{repair.id}" + selector = "a[data-method='delete'][href='#{delete_path}']" + + # wait for the link to appear + expect(page).to have_selector(selector, visible: true, wait: 10) + + # click it under the confirm dialog, retrying if needed retries = 3 begin accept_confirm do - find("a[id='delete_in_house_repair_record_1']", wait: 10).click + find(selector, visible: true, wait: 10).click end rescue Capybara::ModalNotFound retries -= 1 retry if retries.positive? raise end + + # verify the record has been removed from the page expect(page).to have_no_text('Mend paper performed by Haritha Vytla', wait: 10) # Create External Repair @@ -358,12 +367,30 @@ expect(page).to have_text('Wash performed by Amanda Buck. Other note: Some Other note for the external repair', wait: 10) # Delete External Repair - expect(page).to have_selector("a[id='delete_external_repair_record_1']", wait: 10) - accept_confirm(wait: 15) do - find("a[id='delete_external_repair_record_1']", wait: 10).click + repair = ExternalRepairRecord.last + record_id = page.current_path.split('/')[2] # grabs the :conservation_record id from “/conservation_records/5” + delete_path = "/conservation_records/#{record_id}/external_repair_records/#{repair.id}" + selector = "a[data-method='delete'][href='#{delete_path}']" + + # wait for the link to appear + expect(page).to have_selector(selector, visible: true, wait: 10) + + # click it under the confirm dialog, retrying if needed + retries = 3 + begin + accept_confirm do + find(selector, visible: true, wait: 10).click + end + rescue Capybara::ModalNotFound + retries -= 1 + retry if retries.positive? + raise end + + # assert it’s gone expect(page).to have_no_text('Wash performed by Amanda Buck', wait: 10) + # Conservators and Technicians expect(page).to have_button('Add Conservators and Technicians', wait: 10) find('button', text: 'Add Conservators and Technicians', wait: 10).click diff --git a/spec/features/read_only_user_spec.rb b/spec/features/read_only_user_spec.rb index cb05f9e64..c31ddc6ea 100644 --- a/spec/features/read_only_user_spec.rb +++ b/spec/features/read_only_user_spec.rb @@ -28,7 +28,7 @@ click_link(conservation_record.title, match: :prefer_exact) expect(page).to have_content(conservation_record.title) expect(page).to have_no_link('Edit Conservation Record') - expect(page).to have_no_button('Add In-House Repairs') + expect(page).to have_no_button('Add In-House Repair') expect(page).to have_no_button('Add External Repair') expect(page).to have_no_button('Add Conservators and Technicians') expect(page).to have_button('Save Treatment Report', disabled: true) diff --git a/spec/features/standard_user_spec.rb b/spec/features/standard_user_spec.rb index 94ad87cc3..05d2c3373 100644 --- a/spec/features/standard_user_spec.rb +++ b/spec/features/standard_user_spec.rb @@ -59,8 +59,8 @@ visit conservation_records_path click_link(conservation_record.title, match: :prefer_exact) - expect(page).to have_button('Add In-House Repairs') - click_button('Add In-House Repairs') + expect(page).to have_button('Add In-House Repair') + click_button('Add In-House Repair') select('Chuck Greenman', from: 'in_house_performed_by_user_id') select('Soft slipcase', from: 'in_house_repair_type', match: :first) fill_in 'in_house_other_note', with: 'Other Note' diff --git a/spec/requests/conservation_records/index_all_conservation_records_spec.rb b/spec/requests/conservation_records/index_all_conservation_records_spec.rb new file mode 100644 index 000000000..b0154653e --- /dev/null +++ b/spec/requests/conservation_records/index_all_conservation_records_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'GET /conservation_records', type: :request do + include_context 'conservation_records setup' + + describe 'index' do + context 'when not signed in' do + include_examples 'requires authentication', + :conservation_records_path, + 'You must be signed in to access this page.' + end + + context 'when inactive' do + before do + # pretend we’re signed in as an inactive user + allow_any_instance_of(ApplicationController) + .to receive(:current_user).and_return(inactive_user) + + get conservation_records_path + end + + it 'redirects to root with inactive alert' do + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to eq('Your account is not active.') + end + end + + context 'as read_only' do + before { login_as(read_only_user, conservation_records_path) } + include_examples 'index permissions', 'read_only', false, false + end + + context 'as standard' do + before { login_as(standard_user, conservation_records_path) } + include_examples 'index permissions', 'standard', true, true + end + + context 'as admin' do + before { login_as(admin_user, conservation_records_path) } + include_examples 'index permissions', 'admin', true, true + end + end +end diff --git a/spec/requests/conservation_records/show_specific_conservation_record_spec.rb b/spec/requests/conservation_records/show_specific_conservation_record_spec.rb new file mode 100644 index 000000000..df494c6d7 --- /dev/null +++ b/spec/requests/conservation_records/show_specific_conservation_record_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'GET /conservation_records/:id', type: :request do + include_context 'conservation_records setup' + + let(:path) { conservation_record_path(conservation_record) } + + describe 'show' do + context 'when not signed in' do + include_examples 'requires authentication', + :conservation_records_path, + 'You must be signed in to access this page.' + end + + context 'when inactive' do + before do + # Pretend we’re already signed in as an inactive user + # since we can't really sign in as an inactive user. + # This covers a situation where a user has been made + # inactive while they are logged in. + allow_any_instance_of(ApplicationController) + .to receive(:current_user) + .and_return(inactive_user) + end + + it 'redirects to root with inactive alert' do + get conservation_records_path + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to eq('Your account is not active.') + end + end + + context 'as read_only' do + before { login_as(read_only_user, path) } + include_examples 'show permissions', 'read_only', false, false + end + + context 'as standard' do + before { login_as(standard_user, path) } + include_examples 'show permissions', 'standard', true, true + + it 'renders report tabs' do + doc = Nokogiri::HTML(response.body) + expect(doc.at_css('ul#reportTab.nav-pills')).not_to be_nil + expect( + doc.at_xpath("//ul[@id='reportTab']//a[contains(normalize-space(.), 'Treatment Report')]") + ).not_to be_nil + expect( + doc.at_xpath("//ul[@id='reportTab']//a[contains(normalize-space(.), 'Abbreviated Treatment Report')]") + ).not_to be_nil + end + + it 'renders cost & return form' do + doc = Nokogiri::HTML(response.body) + expect(doc.at_css('h3#cost-and-return-information')).not_to be_nil + expect(doc.at_css('form.disable_input')).not_to be_nil + + %w[shipping_cost repair_estimate repair_cost invoice_sent_to_business_office].each do |field| + selector = "input[name*='cost_return_report'][name$='[#{field}]']" + expect(doc.at_css(selector)).not_to be_nil + end + end + + it 'renders abbreviated report download link' do + doc = Nokogiri::HTML(response.body) + abr_link = doc.at_xpath( + "//a[contains(normalize-space(.),'Download Abbreviated Treatment Report')]" + ) + expect(abr_link).not_to be_nil + end + end + + context 'as admin' do + before { login_as(admin_user, path) } + include_examples 'show permissions', 'admin', true, true + + it 'returns 404 for nonexistent ID' do + get conservation_record_path(id: 'does-not-exist') + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/conservation_records_spec.rb b/spec/requests/conservation_records_spec.rb deleted file mode 100644 index 2f7bb7fbd..000000000 --- a/spec/requests/conservation_records_spec.rb +++ /dev/null @@ -1,328 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require 'nokogiri' - -RSpec.describe 'ConservationRecords', type: :request do - let(:read_only_user) { create(:user, role: 'read_only') } - let(:standard_user) { create(:user, role: 'standard') } - let(:admin_user) { create(:user, role: 'admin') } - let(:inactive_user) { create(:user, role: 'standard', account_active: false) } - let!(:conservation_record) { create(:conservation_record) } - - describe 'GET /conservation_records' do - context 'when user is read_only' do - before do - request_login_as(read_only_user, target: conservation_records_path) - end - - it 'shows the conservation records page with appropriate content for the user' do - expect(response).to have_http_status(200) - - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - # Check for the presence of an h1 with the text "Conservation Records" - expect(parsed_body.at_css('h1').text).to eq('Conservation Records') - - # Check for the presence of specific content within the parsed HTML - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - - # Check for the absence of a specific link with the text "New Conservation Record" - expect(parsed_body.at_css('a:contains("New Conservation Record")')).to be_nil - - # Check for the presence of a link with the conservation record's title and correct href - expect(parsed_body.at_css("a[href='#{conservation_record_path(conservation_record)}']").text).to eq(conservation_record.title) - - # Check for the absence of an img tag with a delete icon and alt="Delete" - expect(parsed_body.at_css('img.delete-icon[alt="Delete"]')).to be_nil - end - end - - context 'when user is standard' do - before do - request_login_as(standard_user, target: conservation_records_path) - end - - it 'shows the conservation records page with appropriate content for the user' do - expect(response).to have_http_status(200) - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - # Check for the presence of an h1 with the text "Conservation Records" - expect(parsed_body.at_css('h1').text).to eq('Conservation Records') - - # Check for the presence of specific content within the parsed HTML - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - - # Check for the presence of a specific link with the text "New Conservation Record" - expect(parsed_body.at_css('a:contains("New Conservation Record")')).not_to be_nil - - # Check for the presence of a link with the conservation record's title and correct href - expect(parsed_body.at_css("a[href='#{conservation_record_path(conservation_record)}']").text).to eq(conservation_record.title) - - # Check for the presence of an img tag with a delete icon and alt="Delete" - expect(parsed_body.at_css('img.delete-icon[alt="Delete"]')).not_to be_nil - end - end - - context 'when user is admin' do - before do - request_login_as(admin_user, target: conservation_records_path) - end - - it 'shows the conservation records page with appropriate content for the user' do - expect(response).to have_http_status(200) - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - # Check for the presence of an h1 with the text "Conservation Records" - expect(parsed_body.at_css('h1').text).to eq('Conservation Records') - - # Check for the presence of specific text content within the parsed HTML - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - - # Check for the presence of a link with href for creating a new conservation record - expect(parsed_body.at_css("a[href='#{new_conservation_record_path}']").text).to eq('New Conservation Record') - - # Check for the presence of a link with the conservation record's title and correct href - expect(parsed_body.at_css("a[href='#{conservation_record_path(conservation_record)}']").text).to eq(conservation_record.title) - - # Check for the presence of an img tag with a delete icon and alt="Delete" - expect(parsed_body.at_css('img.delete-icon[alt="Delete"]')).not_to be_nil - end - end - - context 'when user is not logged in' do - it 'redirects to root page' do - get controlled_vocabularies_path - expect(response).to redirect_to(root_path) - expect(flash[:alert]).to eq('You must be signed in to access this page.') - end - end - - context 'when user is inactive' do - before do - request_login_as(inactive_user, target: controlled_vocabularies_path) - end - - it 'shows an alert' do - expect(response).to redirect_to root_path - expect(flash[:alert]).to eq('Your account is not active.') - end - end - end - - describe 'GET /conservation_records/:id' do - context 'when user is read_only' do - before do - request_login_as(read_only_user, target: conservation_record_path(conservation_record)) - end - - it 'shows the conservation record with appropriate permissions' do - expect(response).to have_http_status(200) - - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - expect(parsed_body.at_css("a[href='#{conservation_records_path}']:contains('← Return to List')")).not_to be_nil - expect(parsed_body.at_css('h1').text).to include(conservation_record.title) - expect(parsed_body.at_css('a:contains("Edit Conservation Record")')).to be_nil - expect(parsed_body.at_css('a:contains("Download Conservation Worksheet")')).not_to be_nil - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - expect(parsed_body.at_css('h3:contains("In-House Repairs")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Add In-House Repairs")')).to be_nil - expect(parsed_body.at_css('h3:contains("External Repairs")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Add External Repair")')).to be_nil - expect(parsed_body.at_css('h3:contains("Conservators and Technicians")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Add Conservators and Technicians")')).to be_nil - expect(parsed_body.at_css('h3:contains("Treatment Report")')).not_to be_nil - expect(parsed_body.at_css('h3:contains("Cost and Return Information")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Download Abbreviated Treatment Report")')).not_to be_nil - end - end - - context 'when user is standard' do - before do - request_login_as(standard_user, target: conservation_record_path(conservation_record)) - end - - it 'shows the conservation record with appropriate permissions' do - expect(response).to have_http_status(200) - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - # Check for the presence of a link with href for returning to the list - expect(parsed_body.at_css("a[href='#{conservation_records_path}'][text()='← Return to List']")).not_to be_nil - - # Check for the presence of an h1 with the conservation record title - expect(parsed_body.at_css('h1').text).to eq(conservation_record.title) - - # Check for the presence of a link to edit the conservation record - expect(parsed_body.at_css("a[href='#{edit_conservation_record_path(conservation_record)}']").text).to eq('Edit Conservation Record') - - # Check for the presence of a link to download the conservation worksheet - expect(parsed_body.at_css('a:contains("Download Conservation Worksheet")')).not_to be_nil - - # Check for the presence of specific text content within the parsed HTML - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - - expect(parsed_body.at_css('h3:contains("In-House Repairs")')).not_to be_nil - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#inHouseRepairModal"]' - ).text.strip - ).to eq('Add In-House Repairs') - - expect(parsed_body.at_css('h3:contains("External Repairs")')).not_to be_nil - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#externalRepairModal"]' - ).text.strip - ).to eq('Add External Repair') - - expect(parsed_body.at_css('h3:contains("Conservators and Technicians")')).not_to be_nil - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#ConservatorsTechniciansModal"]' - ).text.strip - ).to eq('Add Conservators and Technicians') - - expect(parsed_body.at_css('h3:contains("Treatment Report")')).not_to be_nil - expect( - parsed_body.at_css( - 'input[type="submit"]' \ - '[name="commit"]' \ - '[value="Save Treatment Report"]' \ - '[class="btn btn-primary"]' \ - '[data-disable-with="Save Treatment Report"]' - ) - ).not_to be_nil - - expect(parsed_body.at_css('h3:contains("Cost and Return Information")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Download Abbreviated Treatment Report")')).not_to be_nil - end - end - - context 'when user is admin' do - before do - request_login_as(admin_user, target: conservation_record_path(conservation_record)) - end - - it 'shows the conservation record with appropriate permissions' do - expect(response).to have_http_status(200) - # Parse the response body as HTML - parsed_body = Nokogiri::HTML(response.body) - - # Check for the presence of a link with href for returning to the list - expect(parsed_body.at_css("a[href='#{conservation_records_path}'][text()='← Return to List']")).not_to be_nil - - # Check for the presence of an h1 with the conservation record title - expect(parsed_body.at_css('h1').text).to eq(conservation_record.title) - - # Check for the presence of a link to edit the conservation record - expect(parsed_body.at_css("a[href='#{edit_conservation_record_path(conservation_record)}']").text).to eq('Edit Conservation Record') - - # Check for the presence of a link to download the conservation worksheet - expect(parsed_body.at_css('a:contains("Download Conservation Worksheet")')).not_to be_nil - - # Check for the presence of specific text content within the parsed HTML - expect(parsed_body.text).to include(conservation_record.id.to_s) - expect(parsed_body.text).to include(conservation_record.title) - expect(parsed_body.text).to include(conservation_record.author) - expect(parsed_body.text).to include(conservation_record.call_number.to_s) - expect(parsed_body.text).to include(conservation_record.item_record_number.to_s) - - expect(parsed_body.at_css('h3:contains("In-House Repairs")')).not_to be_nil - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#inHouseRepairModal"]' - ).text.strip - ).to eq('Add In-House Repairs') - - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#externalRepairModal"]' - ).text.strip - ).to eq('Add External Repair') - - expect(parsed_body.at_css('h3:contains("External Repairs")')).not_to be_nil - expect(parsed_body.at_css('h3:contains("Conservators and Technicians")')).not_to be_nil - expect( - parsed_body.at_css( - 'button.btn.btn-primary.cta-btn' \ - '[data-bs-toggle="modal"]' \ - '[data-bs-target="#ConservatorsTechniciansModal"]' - ).text.strip - ).to eq('Add Conservators and Technicians') - - expect(parsed_body.at_css('h3:contains("Treatment Report")')).not_to be_nil - expect( - parsed_body.at_css( - 'input[type="submit"][name="commit"]' \ - '[value="Save Treatment Report"]' \ - '[class="btn btn-primary"]' \ - '[data-disable-with="Save Treatment Report"]' - ) - ).not_to be_nil - - expect(parsed_body.at_css('h3:contains("Cost and Return Information")')).not_to be_nil - expect(parsed_body.at_css('a:contains("Download Abbreviated Treatment Report")')).not_to be_nil - end - - it 'returns a 404 for a non-existent conservation record' do - get conservation_record_path(id: 'non-existent-id') - expect(response).to have_http_status(404) - end - end - - context 'when user is not logged in' do - it 'redirects to root page' do - get conservation_record_path(conservation_record) - expect(response).to redirect_to(root_path) - expect(flash[:alert]).to eq('You must be signed in to access this page.') - end - end - - context 'when user is inactive' do - before do - request_login_as(inactive_user, target: conservation_record_path(conservation_record)) - end - - it 'redirects to root path with alert' do - expect(response).to redirect_to(root_path) - expect(flash[:alert]).to eq('Your account is not active.') - end - end - end -end diff --git a/spec/support/conservation_records_shared_examples.rb b/spec/support/conservation_records_shared_examples.rb new file mode 100644 index 000000000..3db1ff3f0 --- /dev/null +++ b/spec/support/conservation_records_shared_examples.rb @@ -0,0 +1,116 @@ +# spec/support/conservation_records_shared_examples.rb +# frozen_string_literal: true + +require 'nokogiri' + +RSpec.shared_context 'conservation_records setup' do + let(:read_only_user) { create(:user, role: 'read_only') } + let(:standard_user) { create(:user, role: 'standard') } + let(:admin_user) { create(:user, role: 'admin') } + let(:inactive_user) { create(:user, role: 'standard', account_active: false) } + let!(:conservation_record) { create(:conservation_record) } + + let(:record_texts) do + [ + conservation_record.id.to_s, + conservation_record.title, + conservation_record.author, + conservation_record.call_number.to_s, + conservation_record.item_record_number.to_s + ] + end + + def login_as(user, path) + request_login_as(user, target: path) + end +end + +RSpec.shared_examples 'requires authentication' do |path_method, *args, flash_message| + it 'redirects to root with alert' do + get send(path_method, *args) + expect(response).to redirect_to(root_path) + expect(flash[:alert]).to eq(flash_message) + end +end + +RSpec.shared_examples 'index permissions' do |role, can_create, can_delete| + it "#{role} sees correct index controls" do + expect(response).to have_http_status(:ok) + doc = Nokogiri::HTML(response.body) + + # header and list contents + expect(doc.at_css('h1').text).to eq('Conservation Records') + record_texts.each { |txt| expect(doc.text).to include(txt) } + + # New link + if can_create + expect(doc.at_css("a[href='#{new_conservation_record_path}']")).not_to be_nil + else + expect(doc.at_xpath("//a[text()='New Conservation Record']")).to be_nil + end + + # Delete icons + imgs = doc.css('img.delete-icon[alt="Delete"]') + expect(imgs.any?).to eq(can_delete) + end +end + +RSpec.shared_examples 'show permissions' do |role, can_edit, can_add| + it "#{role} sees correct show controls" do + expect(response).to have_http_status(:ok) + doc = Nokogiri::HTML(response.body) + + # back‐to‐list link + return_link = doc.at_xpath( + "//a[@href='#{conservation_records_path}' and contains(normalize-space(.),'← Return to List')]" + ) + expect(return_link).not_to be_nil + + # page title + expect(doc.at_css('h1').text).to include(conservation_record.title) + + # worksheet download link + worksheet_link = doc.at_xpath( + "//a[contains(normalize-space(.), 'Download Conservation Worksheet')]" + ) + expect(worksheet_link).not_to be_nil + + # edit link + if can_edit + edit_link = doc.at_css("a[href='#{edit_conservation_record_path(conservation_record)}']") + expect(edit_link).not_to be_nil + expect(edit_link.text.strip).to eq('Edit Conservation Record') + else + expect( + doc.at_xpath("//a[contains(normalize-space(.), 'Edit Conservation Record')]") + ).to be_nil + end + + # detail fields + record_texts.each { |txt| expect(doc.text).to include(txt) } + + # repair-section headers & modal triggers + { + 'In-House Repairs' => 'inHouseRepairModal', + 'External Repairs' => 'externalRepairModal', + 'Conservators and Technicians' => 'ConservatorsTechniciansModal' + }.each do |section_text, modal_id| + # header + header_xpath = "//h3[contains(normalize-space(.), '#{section_text}')]" + expect(doc.at_xpath(header_xpath)).not_to be_nil + + # modal-trigger button by data attribute + btn_selector = "button.cta-btn[data-bs-toggle='modal'][data-bs-target='##{modal_id}']" + if can_add + expect(doc.at_css(btn_selector)).not_to be_nil + else + expect(doc.at_css(btn_selector)).to be_nil + end + end + + # modal containers always present in DOM + expect(doc.at_css('#inHouseRepairModal.modal')).not_to be_nil + expect(doc.at_css('#externalRepairModal.modal')).not_to be_nil + expect(doc.at_css('#ConservatorsTechniciansModal.modal')).not_to be_nil + end +end diff --git a/spec/views/conservation_records/show.html.erb_spec.rb b/spec/views/conservation_records/show.html.erb_spec.rb index 7e75bf301..045c4f903 100644 --- a/spec/views/conservation_records/show.html.erb_spec.rb +++ b/spec/views/conservation_records/show.html.erb_spec.rb @@ -54,7 +54,7 @@ view_stub_authorization(user) render expect(rendered).not_to have_link('Edit Conservation Record') - expect(rendered).not_to have_button('Add In-House Repairs') + expect(rendered).not_to have_button('Add In-House Repair') expect(rendered).not_to have_button('Add External Repair') expect(rendered).not_to have_button('Add Conservators and Technicians') expect(rendered).not_to have_button('Delete In-House Repair')