diff --git a/.allow_skipping_tests b/.allow_skipping_tests index fdaf9ab7c6..906e7b14e9 100644 --- a/.allow_skipping_tests +++ b/.allow_skipping_tests @@ -1,51 +1,14 @@ channels/application_cable/channel.rb channels/application_cable/connection.rb -controllers/all_casa_admins/casa_admins_controller.rb -controllers/all_casa_admins/dashboard_controller.rb -controllers/all_casa_admins/patch_notes_controller.rb -controllers/all_casa_admins/sessions_controller.rb -controllers/all_casa_admins_controller.rb -controllers/android_app_associations_controller.rb -controllers/application_controller.rb -controllers/casa_admins_controller.rb -controllers/casa_org_controller.rb -controllers/case_assignments_controller.rb -controllers/case_contact_reports_controller.rb -controllers/case_contacts/followups_controller.rb -controllers/case_contacts_controller.rb -controllers/case_court_orders_controller.rb -controllers/case_court_reports_controller.rb -controllers/checklist_items_controller.rb -controllers/contact_type_groups_controller.rb -controllers/contact_types_controller.rb -controllers/court_dates_controller.rb -controllers/dashboard_controller.rb -controllers/emancipation_checklists_controller.rb -controllers/error_controller.rb +controllers/banners_controller.rb +controllers/bulk_court_dates_controller.rb +controllers/case_groups_controller.rb +controllers/concerns/court_date_params.rb controllers/followup_reports_controller.rb -controllers/fund_requests_controller.rb -controllers/health_controller.rb -controllers/hearing_types_controller.rb -controllers/imports_controller.rb -controllers/judges_controller.rb -controllers/languages_controller.rb -controllers/learning_hours_controller.rb -controllers/learning_hours_reports_controller.rb -controllers/mileage_rates_controller.rb +controllers/learning_hour_types_controller.rb controllers/mileage_reports_controller.rb -controllers/missing_data_reports_controller.rb -controllers/notes_controller.rb -controllers/notifications_controller.rb -controllers/other_duties_controller.rb controllers/placements_controller.rb -controllers/placement_reports_controller.rb -controllers/reports_controller.rb -controllers/supervisor_volunteers_controller.rb -controllers/supervisors_controller.rb -controllers/users/passwords_controller.rb controllers/users/sessions_controller.rb -controllers/users_controller.rb -controllers/volunteers_controller.rb datatables/application_datatable.rb decorators/application_decorator.rb decorators/case_assignment_decorator.rb @@ -53,7 +16,6 @@ decorators/court_date_decorator.rb decorators/learning_hour_decorator.rb helpers/all_casa_admins/casa_orgs_helper.rb helpers/api_base_helper.rb -helpers/contact_types_helper.rb helpers/date_helper.rb helpers/request_header_helper.rb helpers/template_helper.rb @@ -71,9 +33,9 @@ notifications/emancipation_checklist_reminder_notification.rb notifications/followup_notification.rb notifications/followup_resolved_notification.rb notifications/youth_birthday_notification.rb -policies/case_court_order_policy.rb policies/fund_request_policy.rb policies/learning_hour_policy.rb +policies/learning_hour_type_policy.rb policies/note_policy.rb presenters/base_presenter.rb presenters/case_contact_presenter.rb @@ -82,7 +44,6 @@ services/court_report_format_contact_date.rb services/create_all_casa_admin_service.rb services/create_casa_admin_service.rb services/fdf_inputs_service.rb -services/mileage_export_csv_service.rb validators/casa_org_validator.rb validators/court_report_validator.rb validators/user_validator.rb diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e52f5972a2..4a21ac5f2f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,29 +5,30 @@ title: "Bug: " labels: ["Type: Bug", "Help Wanted"] --- -### Impacted User Types +## Impacted User Types - volunteers? - supervisors? - admins? - all casa admins? -### Environment +## Environment ex: staging, desktop web, Safari -### Current Behavior +## Current Behavior ex: When I click "generate report," no report is generated. _Please include a screenshot!_ -### Expected Behavior +## Expected Behavior ex: When I click "generate report", a downloadable report should be generated. -### How to Replicate +## How to Replicate ex: 1. - Log in as an admin or supervisor. 2. - Click on "Generate Reports" in the left sidebar menu. 3. - Filter by a volunteer who has logged at least one case contact. 4. - Click "Generate report" at the bottom of the page. +## How to access the QA site _Login Details:_ [Link to QA site](https://casa-qa.herokuapp.com/) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e3dfc3ee21..80bb0f47d8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,18 +4,19 @@ about: 🔨What needs building? ✨ labels: ["✨ New Feature", "Help Wanted"] --- -**What type(s) of user does this feature affect?** +## What type(s) of user does this feature affect? - volunteers? - supervisors? - admins? - all casa admins? -**Description** +## Description -**Screenshots of current behavior, if any** +## Screenshots of current behavior, if any You can paste images on the clipboard here +## How to access the QA site _Login Details:_ [Link to QA site](https://casa-qa.herokuapp.com/) diff --git a/.github/ISSUE_TEMPLATE/flaky_test.md b/.github/ISSUE_TEMPLATE/flaky_test.md index 7899c52152..58ce6ddf10 100644 --- a/.github/ISSUE_TEMPLATE/flaky_test.md +++ b/.github/ISSUE_TEMPLATE/flaky_test.md @@ -7,19 +7,13 @@ labels: ["Type: Bug", "Help Wanted"] Flaky tests are defined as tests that return both passes and failures despite no changes to the code or the test itself Fix the test so it runs consistently. - - -### Environment -ex: docker +### CI Workflow +rspec or docker? ### Sample Error Output: ``` ``` -### How to Replicate -Try running the test lots of times locally -`bundle exec rspec spec/...` - ### Questions? Join Slack! We highly recommend that you join us in slack https://rubyforgood.herokuapp.com/ #casa channel to ask questions quickly and hear about office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues. diff --git a/Gemfile b/Gemfile index 6ea22ecfc3..9530ea3b8e 100644 --- a/Gemfile +++ b/Gemfile @@ -43,6 +43,7 @@ gem "request_store" gem "sablon" # Word document templating tool for Case Court Reports gem "scout_apm" gem "sprockets-rails" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] +gem "stimulus-rails" gem "strong_migrations" gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "active_model_serializers" # for JSON serialization @@ -62,6 +63,7 @@ end group :development do gem "annotate" # for adding db field listings to models as comments + gem "bundler-audit" # for checking for security issues in gems gem "letter_opener" # Opens emails in new tab for easier testing gem "spring" # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem "spring-commands-rspec" @@ -79,7 +81,7 @@ group :test do gem "rake" gem "selenium-webdriver" gem "simplecov" - gem "webdrivers" # easy installation and use of web drivers to run system tests with browsers + gem "docx" end # gem "pdf-reader", "~> 2.9" diff --git a/Gemfile.lock b/Gemfile.lock index dca95df1f1..e609550d98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,13 +100,16 @@ GEM parser (>= 2.4) smart_properties bindex (0.8.1) - brakeman (6.0.0) - bugsnag (6.25.2) + brakeman (6.0.1) + bugsnag (6.26.0) concurrent-ruby (~> 1.0) builder (3.2.4) bullet (7.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) + bundler-audit (0.9.1) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) byebug (11.1.3) capybara (3.39.2) addressable @@ -160,6 +163,9 @@ GEM devise (>= 4.6) diff-lcs (1.5.0) docile (1.4.0) + docx (0.8.0) + nokogiri (~> 1.13, >= 1.13.0) + rubyzip (~> 2.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) @@ -254,7 +260,7 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) - lograge (0.12.0) + lograge (0.13.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -272,12 +278,12 @@ GEM method_source (1.0.0) mini_magick (4.11.0) mini_mime (1.1.2) - minitest (5.18.1) + minitest (5.19.0) multi_xml (0.6.0) multipart-post (2.3.0) net-http-persistent (4.0.1) connection_pool (~> 2.2) - net-imap (0.3.6) + net-imap (0.3.7) date net-protocol net-pop (0.1.2) @@ -287,11 +293,11 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) - nokogiri (1.15.2-arm64-darwin) + nokogiri (1.15.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.2-x86_64-darwin) + nokogiri (1.15.3-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.2-x86_64-linux) + nokogiri (1.15.3-x86_64-linux) racc (~> 1.4) noticed (1.6.3) http (>= 4.0.0) @@ -317,10 +323,10 @@ GEM public_suffix (5.0.1) puma (6.3.0) nio4r (~> 2.0) - pundit (2.3.0) + pundit (2.3.1) activesupport (>= 3.0.0) racc (1.7.1) - rack (2.2.7) + rack (2.2.8) rack-attack (6.6.1) rack (>= 1.0, < 3) rack-cors (2.0.1) @@ -367,7 +373,7 @@ GEM responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.5) + rexml (3.2.6) rspec-core (3.12.2) rspec-support (~> 3.12.0) rspec-expectations (3.12.3) @@ -436,13 +442,15 @@ GEM standard (1.5.0) rubocop (= 1.23.0) rubocop-performance (= 1.12.0) - strong_migrations (1.5.0) + stimulus-rails (1.2.1) + railties (>= 6.0.0) + strong_migrations (1.6.0) activerecord (>= 5.2) thor (1.2.2) timeout (0.4.0) traceroute (0.8.1) rails (>= 3.0.0) - twilio-ruby (6.2.0) + twilio-ruby (6.3.1) faraday (>= 0.9, < 3.0) jwt (>= 1.5, < 3.0) nokogiri (>= 1.6, < 2.0) @@ -453,7 +461,7 @@ GEM unf_ext (0.0.8.2) unicode-display_width (2.4.2) uniform_notifier (1.16.0) - view_component (3.3.0) + view_component (3.5.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -464,10 +472,6 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -478,7 +482,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.8) + zeitwerk (2.6.10) PLATFORMS arm64-darwin-20 @@ -500,6 +504,7 @@ DEPENDENCIES brakeman bugsnag bullet + bundler-audit byebug capybara capybara-screenshot @@ -510,6 +515,7 @@ DEPENDENCIES delayed_job_active_record devise devise_invitable + docx dotenv-rails draper erb_lint @@ -552,13 +558,13 @@ DEPENDENCIES spring-commands-rspec sprockets-rails standard (= 1.5.0) + stimulus-rails strong_migrations traceroute twilio-ruby tzinfo-data view_component web-console (>= 3.3.0) - webdrivers webmock RUBY VERSION diff --git a/README.md b/README.md index 4ba98e904f..f4a03a71ca 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A CASA (Court Appointed Special Advocate) is a role where a volunteer advocates - [Common issues](#common-issues) - [Running the App / Verifying Installation](#running-the-app--verifying-installation) - [Other Documentation](#other-documentation) -- [required acknowledgement](#required-acknowledgement) +- [Acknowledgements](#acknowledgements) - [Communication and Collaboration](#communication-and-collaboration) - [Feedback](#feedback) @@ -201,6 +201,12 @@ Test coverage is run by simplecov on all builds and aggregated by CodeClimate If additional work arises from your pull request that is outside the scope of the issue it resolves, please open a new issue. +**Stimulus** + +[Issue 5016](https://github.com/rubyforgood/casa/issues/5016) started a refactor of Javascript to use +[Hotwire's Stimulus](https://stimulus.hotwired.dev/handbook/origin). To see if it's working for you, go to +`/casa_cases` and see **Stimulus is working!** in your browser console. + **Post-deployment tasks** We are using [After Party](https://github.com/theSteveMitchell/after_party) to @@ -228,10 +234,10 @@ There is a `doc` directory at the top level that includes: * [productsense.md](doc/productsense.md)(for team leads & product interested contributors) * [SECURITY.md](doc/SECURITY.md) -# required acknowledgement +# Acknowledgements Thank you to [Scout](https://ter.li/h8k29r) for letting us use their dashboard for free! -[![Scout](https://user-images.githubusercontent.com/578159/165240278-c2c0ac30-c86f-4b67-9da6-e6a5e4ab4c37.png)](https://ter.li/h8k29r) +[](https://ter.li/h8k29r) # Communication and Collaboration diff --git a/Rakefile b/Rakefile index 9a5ea7383a..30760b1b4a 100644 --- a/Rakefile +++ b/Rakefile @@ -4,3 +4,8 @@ require_relative "config/application" Rails.application.load_tasks + +if Rails.env.development? + require "bundler/audit/task" + Bundler::Audit::Task.new +end diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css new file mode 100644 index 0000000000..3cfcb2b75f --- /dev/null +++ b/app/assets/stylesheets/actiontext.css @@ -0,0 +1,31 @@ +/* + * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and + * the trix-editor content (whether displayed or under editing). Feel free to incorporate this + * inclusion directly in any other asset bundle and remove this file. + * + *= require trix +*/ + +/* + * We need to override trix.css’s image gallery styles to accommodate the + * element we wrap around attachments. Otherwise, + * images in galleries will be squished by the max-width: 33%; rule. +*/ +.trix-content .attachment-gallery > action-text-attachment, +.trix-content .attachment-gallery > .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; +} + +.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--4 > .attachment { + flex-basis: 50%; + max-width: 50%; +} + +.trix-content action-text-attachment .attachment { + padding: 0 !important; + max-width: 100% !important; +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 664c643d34..9bb9dfbbbe 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -8,7 +8,6 @@ @use "@fortawesome/fontawesome-free/scss/fontawesome"; @use "@fortawesome/fontawesome-free/scss/solid"; @use "select2/dist/css/select2"; -@use "select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme"; // custom css for the app @use "base/breakpoints"; @@ -38,3 +37,6 @@ @use "pages/reimbursements"; @use "pages/supervisors"; @use "pages/volunteers"; + +@use 'trix/dist/trix.css'; +@use "actiontext"; diff --git a/app/assets/stylesheets/pages/patch_notes.scss b/app/assets/stylesheets/pages/patch_notes.scss index a4f19ed234..60d112b3d2 100644 --- a/app/assets/stylesheets/pages/patch_notes.scss +++ b/app/assets/stylesheets/pages/patch_notes.scss @@ -5,7 +5,7 @@ width: 50%; h1 { - text-align: center + text-align: left; } .patch-note-list-item { diff --git a/app/assets/stylesheets/shared/sidebar.scss b/app/assets/stylesheets/shared/sidebar.scss index c0b18fa4c8..1ee4ca688c 100644 --- a/app/assets/stylesheets/shared/sidebar.scss +++ b/app/assets/stylesheets/shared/sidebar.scss @@ -113,6 +113,16 @@ } } +.group-actions { + &:hover::before { + opacity: 1 !important; + visibility: visible !important; + } + &::before { + opacity: 0 !important; + visibility: hidden !important; + } +} // Media Queries @media only screen and (max-width: screen-sizes.$mobile) { .sidebar-wrapper { diff --git a/app/controllers/banners_controller.rb b/app/controllers/banners_controller.rb new file mode 100644 index 0000000000..3755d97976 --- /dev/null +++ b/app/controllers/banners_controller.rb @@ -0,0 +1,56 @@ +class BannersController < ApplicationController + after_action :verify_authorized + + def index + authorize :application, :admin_or_supervisor? + + @banners = current_organization.banners.includes(:user) + end + + def new + authorize :application, :admin_or_supervisor? + + @banner = Banner.new + end + + def edit + authorize :application, :admin_or_supervisor? + + @banner = current_organization.banners.find(params[:id]) + end + + def create + authorize :application, :admin_or_supervisor? + + @banner = current_organization.banners.build(banner_params) + if @banner.save + redirect_to banners_path + else + render :new + end + end + + def update + authorize :application, :admin_or_supervisor? + + @banner = current_organization.banners.find(params[:id]) + if @banner.update(banner_params) + redirect_to banners_path + else + render :edit + end + end + + def destroy + authorize :application, :admin_or_supervisor? + + current_organization.banners.find(params[:id]).destroy + redirect_to banners_path + end + + private + + def banner_params + params.require(:banner).permit(:active, :content, :name).merge(user: current_user) + end +end diff --git a/app/controllers/bulk_court_dates_controller.rb b/app/controllers/bulk_court_dates_controller.rb new file mode 100644 index 0000000000..85885a40de --- /dev/null +++ b/app/controllers/bulk_court_dates_controller.rb @@ -0,0 +1,61 @@ +class BulkCourtDatesController < ApplicationController + include CourtDateParams + + before_action :require_organization! + + def new + authorize :application, :admin_or_supervisor? + + @court_date = CourtDate.new + end + + def create + authorize :application, :admin_or_supervisor? + + case_group_id = params[:court_date][:case_group_id] + if case_group_id.empty? + @court_date = build_court_date_with_error_message + render :new + return + end + + case_group = current_organization.case_groups.find(case_group_id) + court_dates = build_court_dates(case_group) + + court_date_with_error = create_court_dates(court_dates) + + if court_date_with_error + @court_date = court_date_with_error + render :new + else + redirect_to new_bulk_court_date_path, notice: "#{court_dates.size} #{"court date".pluralize(court_dates.size)} created!" + end + end + + private + + def build_court_date_with_error_message + court_date = CourtDate.new(court_date_params(nil)) + court_date.errors.add(:base, "Case group must be selected.") + court_date + end + + def build_court_dates(case_group) + case_group.casa_cases.map do |casa_case| + CourtDate.new(court_date_params(casa_case).merge(casa_case: casa_case)) + end + end + + def create_court_dates(court_dates) + court_date_with_error = nil + ActiveRecord::Base.transaction do + court_dates.each do |court_date| + if !court_date.save + court_date_with_error = court_date + raise ActiveRecord::Rollback + end + end + end + court_date_with_error + end +end diff --git a/app/controllers/casa_org_controller.rb b/app/controllers/casa_org_controller.rb index d6ff7d9a81..6dda47ad19 100644 --- a/app/controllers/casa_org_controller.rb +++ b/app/controllers/casa_org_controller.rb @@ -3,6 +3,7 @@ class CasaOrgController < ApplicationController before_action :set_contact_type_data, only: %i[edit update] before_action :set_hearing_types, only: %i[edit update] before_action :set_judges, only: %i[edit update] + before_action :set_learning_hour_types, only: %i[edit update] before_action :set_sent_emails, only: %i[edit update] before_action :require_organization! after_action :verify_authorized @@ -68,6 +69,10 @@ def set_judges @judges = Judge.for_organization(@casa_org) end + def set_learning_hour_types + @learning_hour_types = LearningHourType.for_organization(@casa_org) + end + def set_sent_emails @sent_emails = SentEmail.for_organization(@casa_org).order("created_at DESC").limit(10) end diff --git a/app/controllers/case_assignments_controller.rb b/app/controllers/case_assignments_controller.rb index bcd59c25c4..6a7e33d7c8 100644 --- a/app/controllers/case_assignments_controller.rb +++ b/app/controllers/case_assignments_controller.rb @@ -1,5 +1,5 @@ class CaseAssignmentsController < ApplicationController - before_action :load_case_assignment, only: %i[destroy unassign show_hide_contacts] + before_action :load_case_assignment, only: %i[destroy unassign show_hide_contacts reimbursement] after_action :verify_authorized def create @@ -73,6 +73,15 @@ def show_hide_contacts end end + def reimbursement + casa_case = @case_assignment.casa_case + message = "Volunteer allow reimbursement changed from Case #{casa_case.case_number}." + authorize @case_assignment, :reimbursement? + if @case_assignment.update(allow_reimbursement: !@case_assignment.allow_reimbursement) + redirect_to after_action_path(casa_case), notice: message + end + end + private def case_assignment_parent @@ -92,7 +101,7 @@ def after_action_path(resource) end def case_assignment_params - params.require(:case_assignment).permit(:casa_case_id, :volunteer_id) + params.require(:case_assignment).permit(:casa_case_id, :volunteer_id, :reimbursement) end def load_case_assignment diff --git a/app/controllers/case_contacts_controller.rb b/app/controllers/case_contacts_controller.rb index 89622b4de8..27386484bd 100644 --- a/app/controllers/case_contacts_controller.rb +++ b/app/controllers/case_contacts_controller.rb @@ -129,7 +129,7 @@ def restore authorize CasaAdmin case_contact = authorize(current_organization.case_contacts.with_deleted.find(params[:id])) - case_contact.restore(recrusive: true) + case_contact.restore(recursive: true) flash[:notice] = "Contact is successfully restored." redirect_to request.referer end @@ -215,13 +215,18 @@ def update_case_contact_params def current_organization_groups current_organization.contact_type_groups + .includes(:contact_types) .joins(:contact_types) .where(contact_types: {active: true}) .uniq end def all_case_contacts - policy_scope(current_organization.case_contacts).includes(:creator, contact_types: :contact_type_group) + query = policy_scope(current_organization.case_contacts).includes(:creator, contact_types: :contact_type_group) + if params[:casa_case_id].present? + query = query.where(casa_case_id: params[:casa_case_id]) + end + query end def additional_expense_params diff --git a/app/controllers/case_groups_controller.rb b/app/controllers/case_groups_controller.rb new file mode 100644 index 0000000000..b69f98798a --- /dev/null +++ b/app/controllers/case_groups_controller.rb @@ -0,0 +1,50 @@ +class CaseGroupsController < ApplicationController + before_action :require_organization! + before_action :authorize_admin_or_supervisor! + + def index + @case_groups = current_organization.case_groups.includes(:casa_cases) + end + + def new + @case_group = CaseGroup.new + end + + def edit + @case_group = current_organization.case_groups.find(params[:id]) + end + + def create + @case_group = current_organization.case_groups.build(case_group_params) + if @case_group.save + redirect_to case_groups_path, notice: "Case group created!" + else + render :new + end + end + + def update + @case_group = current_organization.case_groups.find(params[:id]) + if @case_group.update(case_group_params) + redirect_to case_groups_path, notice: "Case group updated!" + else + render :new + end + end + + def destroy + case_group = current_organization.case_groups.find(params[:id]) + case_group.destroy + redirect_to case_groups_path, notice: "Case group deleted!" + end + + private + + def case_group_params + params.require(:case_group).permit(:name, casa_case_ids: []) + end + + def authorize_admin_or_supervisor! + authorize :application, :admin_or_supervisor? + end +end diff --git a/app/controllers/concerns/court_date_params.rb b/app/controllers/concerns/court_date_params.rb new file mode 100644 index 0000000000..444b6c565a --- /dev/null +++ b/app/controllers/concerns/court_date_params.rb @@ -0,0 +1,25 @@ +module CourtDateParams + private + + def sanitized_court_date_params(casa_case) + params.require(:court_date).tap do |p| + p[:case_court_orders_attributes]&.reject! do |k, _| + p[:case_court_orders_attributes][k][:text].blank? && p[:case_court_orders_attributes][k][:implementation_status].blank? + end + + p[:case_court_orders_attributes]&.each do |k, _| + p[:case_court_orders_attributes][k][:casa_case_id] = casa_case.id + end + end + end + + def court_date_params(casa_case) + sanitized_court_date_params(casa_case).permit( + :date, + :hearing_type_id, + :judge_id, + :court_report_due_date, + {case_court_orders_attributes: %i[text _destroy implementation_status id casa_case_id]} + ) + end +end diff --git a/app/controllers/court_dates_controller.rb b/app/controllers/court_dates_controller.rb index 3f44bd7245..82784fc9eb 100644 --- a/app/controllers/court_dates_controller.rb +++ b/app/controllers/court_dates_controller.rb @@ -1,4 +1,6 @@ class CourtDatesController < ApplicationController + include CourtDateParams + before_action :set_casa_case before_action :set_court_date, only: %i[edit show generate update destroy] before_action :require_organization! @@ -11,7 +13,7 @@ def show respond_to do |format| format.html {} format.docx do - send_data @court_date.generate_report, + send_data generate_report_to_string(@court_date, params[:time_zone]), type: :docx, filename: "#{@court_date.display_name}.docx", disposition: "attachment", @@ -30,13 +32,9 @@ def edit end def create - @court_date = CourtDate.new(court_dates_params.merge(casa_case: @casa_case)) + @court_date = CourtDate.new(court_date_params(@casa_case).merge(casa_case: @casa_case)) authorize @court_date - if !@court_date.date.nil? - @court_date.court_report_due_date = @court_date.date - 3.weeks - end - if @court_date.save && @casa_case.save redirect_to casa_case_court_date_path(@casa_case, @court_date), notice: "Court date was successfully created." else @@ -46,7 +44,7 @@ def create def update authorize @court_date - if @court_date.update(court_dates_params) + if @court_date.update(court_date_params(@casa_case)) redirect_to casa_case_court_date_path(@casa_case, @court_date), notice: "Court date was successfully updated." else render :edit @@ -73,25 +71,21 @@ def set_court_date @court_date = @casa_case.court_dates.find(params[:id]) end - def sanitized_params - params.require(:court_date).tap do |p| - p[:case_court_orders_attributes]&.reject! do |k, _| - p[:case_court_orders_attributes][k][:text].blank? && p[:case_court_orders_attributes][k][:implementation_status].blank? - end - - p[:case_court_orders_attributes]&.each do |k, _| - p[:case_court_orders_attributes][k][:casa_case_id] = @casa_case.id - end + def generate_report_to_string(court_date, time_zone) + casa_case = court_date.casa_case + casa_case.casa_org.open_org_court_report_template do |template_docx_file| + args = { + volunteer_id: current_user.volunteer? ? current_user.id : casa_case.assigned_volunteers.first&.id, + case_id: casa_case.id, + path_to_template: template_docx_file.to_path, + time_zone: time_zone, + court_date: court_date, + case_court_orders: court_date.case_court_orders + } + context = CaseCourtReportContext.new(args).context + court_report = CaseCourtReport.new(path_to_template: template_docx_file.to_path, context: context) + + court_report.generate_to_string end end - - def court_dates_params - sanitized_params.permit( - :date, - :hearing_type_id, - :judge_id, - :court_report_due_date, - {case_court_orders_attributes: %i[text implementation_status id casa_case_id]} - ) - end end diff --git a/app/controllers/learning_hour_types_controller.rb b/app/controllers/learning_hour_types_controller.rb new file mode 100644 index 0000000000..45ab790d33 --- /dev/null +++ b/app/controllers/learning_hour_types_controller.rb @@ -0,0 +1,46 @@ +class LearningHourTypesController < ApplicationController + before_action :set_learning_hour_type, only: %i[edit update] + after_action :verify_authorized + + def new + authorize LearningHourType + @learning_hour_type = LearningHourType.new + end + + def edit + authorize @learning_hour_type + end + + def create + authorize LearningHourType + @learning_hour_type = LearningHourType.new(learning_hour_type_params) + + if @learning_hour_type.save + redirect_to edit_casa_org_path(current_organization), notice: "Learning Type was successfully created." + else + render :new + end + end + + def update + authorize @learning_hour_type + + if @learning_hour_type.update(learning_hour_type_params) + redirect_to edit_casa_org_path(current_organization), notice: "Learning Type was successfully updated." + else + render :edit + end + end + + private + + def set_learning_hour_type + @learning_hour_type = LearningHourType.find(params[:id]) + end + + def learning_hour_type_params + params.require(:learning_hour_type).permit(:name, :active).merge( + casa_org: current_organization + ) + end +end diff --git a/app/controllers/learning_hours_controller.rb b/app/controllers/learning_hours_controller.rb index db3441573e..93644b210a 100644 --- a/app/controllers/learning_hours_controller.rb +++ b/app/controllers/learning_hours_controller.rb @@ -57,10 +57,12 @@ def set_learning_hour end def learning_hours_params - params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, :user_id, :learning_type) + params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, :user_id, + :learning_hour_type_id) end def update_learning_hours_params - params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, :learning_type) + params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, + :learning_hour_type_id) end end diff --git a/app/controllers/preference_sets_controller.rb b/app/controllers/preference_sets_controller.rb new file mode 100644 index 0000000000..4f72690354 --- /dev/null +++ b/app/controllers/preference_sets_controller.rb @@ -0,0 +1,24 @@ +class PreferenceSetsController < ApplicationController + before_action :skip_authorization, :set_table_name + + def table_state + render json: PreferenceSetTableStateService.new(user_id: current_user.id).table_state( + table_name: @table_name + ) + end + + def table_state_update + render json: PreferenceSetTableStateService.new(user_id: current_user.id).update!( + table_state: params["table_state"], + table_name: @table_name + ) + rescue PreferenceSetTableStateService::TableStateUpdateFailed + render json: {error: "Failed to update table state for '#{@table_name}'"} + end + + private + + def set_table_name + @table_name = params[:table_name] + end +end diff --git a/app/controllers/reimbursements_controller.rb b/app/controllers/reimbursements_controller.rb index caa79c743f..7deebea7c9 100644 --- a/app/controllers/reimbursements_controller.rb +++ b/app/controllers/reimbursements_controller.rb @@ -91,7 +91,7 @@ def get_normalised_time_for_occurred_at_filter(key) end def reimbursement_params - params.require(:case_contact).permit(:reimbursement_complete, :reimbursement_id) + params.require(:case_contact).permit(:reimbursement_complete) end def volunteers_for_filter(reimbursements) diff --git a/app/controllers/supervisor_volunteers_controller.rb b/app/controllers/supervisor_volunteers_controller.rb index 61d0bc5600..a3f9616d6b 100644 --- a/app/controllers/supervisor_volunteers_controller.rb +++ b/app/controllers/supervisor_volunteers_controller.rb @@ -10,7 +10,7 @@ def create supervisor_volunteer.save flash_message = "#{volunteer.display_name} successfully assigned to #{supervisor.display_name}." - redirect_to request.referrer, notice: flash_message + redirect_to request.referer, notice: flash_message end def unassign @@ -22,7 +22,7 @@ def unassign supervisor_volunteer.save! flash_message = "#{volunteer.display_name} was unassigned from #{supervisor.display_name}." - redirect_to request.referrer, notice: flash_message + redirect_to request.referer, notice: flash_message end private diff --git a/app/decorators/casa_case_decorator.rb b/app/decorators/casa_case_decorator.rb index 684be8a1db..4a3a78dfcd 100644 --- a/app/decorators/casa_case_decorator.rb +++ b/app/decorators/casa_case_decorator.rb @@ -43,12 +43,6 @@ def court_report_select_option ] end - def court_order_select_options - CaseCourtOrder.implementation_statuses.map do |status| - [status[0].humanize, status[0]] - end - end - def date_in_care return nil unless object.date_in_care I18n.l(object.date_in_care, format: :youth_date_of_birth) diff --git a/app/decorators/preference_set_decorator.rb b/app/decorators/preference_set_decorator.rb new file mode 100644 index 0000000000..a38a95b4ad --- /dev/null +++ b/app/decorators/preference_set_decorator.rb @@ -0,0 +1,12 @@ +class PreferenceSetDecorator < ApplicationDecorator + delegate_all + + # Define presentation-specific methods here. Helpers are accessed through + # `helpers` (aka `h`). You can override attributes, for example: + # + # def created_at + # helpers.content_tag :span, class: 'time' do + # object.created_at.strftime("%a %m/%d/%y") + # end + # end +end diff --git a/app/helpers/case_contacts_helper.rb b/app/helpers/case_contacts_helper.rb index 26b46397c7..5b5e934f39 100644 --- a/app/helpers/case_contacts_helper.rb +++ b/app/helpers/case_contacts_helper.rb @@ -37,6 +37,17 @@ def thank_you_message ].sample end + def show_volunteer_reimbursement(casa_cases) + if current_user.role == "Volunteer" + show = casa_cases.map do |casa_case| + casa_case.case_assignments.where(volunteer_id: current_user).first&.allow_reimbursement == true + end + show.any? + else + true + end + end + private def send_home diff --git a/app/helpers/court_orders_helper.rb b/app/helpers/court_orders_helper.rb new file mode 100644 index 0000000000..956bcc7af6 --- /dev/null +++ b/app/helpers/court_orders_helper.rb @@ -0,0 +1,7 @@ +module CourtOrdersHelper + def court_order_select_options + CaseCourtOrder.implementation_statuses.map do |status| + [status[0].humanize, status[0]] + end + end +end diff --git a/app/helpers/preference_sets_helper.rb b/app/helpers/preference_sets_helper.rb new file mode 100644 index 0000000000..67b68258bf --- /dev/null +++ b/app/helpers/preference_sets_helper.rb @@ -0,0 +1,2 @@ +module PreferenceSetsHelper +end diff --git a/app/helpers/sidebar_helper.rb b/app/helpers/sidebar_helper.rb index 4c3d020c0b..e4be743388 100644 --- a/app/helpers/sidebar_helper.rb +++ b/app/helpers/sidebar_helper.rb @@ -18,11 +18,20 @@ def menu_item(label:, path:, visible: false) private # private doesn't work in modules. It's here for semantic purposes def active_class(link_path) - url_route_sections = link_path.split("/") - url_route_sections.delete("all_casa_admins") - controller_name = url_route_sections.second - current_page?({controller: controller_name, action: action_name}) ? "active" : "" + if request_path_active?(link_path) + "active" + else + "" + end rescue ActionController::UrlGenerationError "" end + + def request_path_active?(link_path) + # The second check is needed because Sidebar menu item 'Emancipation + # Checklist(s)' contains a redirect if any @casa_transitioning_cases are + # found + (request.path == link_path) || + (link_path == "/emancipation_checklists" && request.path.match("emancipation")) + end end diff --git a/app/javascript/__tests__/court_order_list.test.js b/app/javascript/__tests__/court_order_list.test.js deleted file mode 100644 index fa0b6c861f..0000000000 --- a/app/javascript/__tests__/court_order_list.test.js +++ /dev/null @@ -1,264 +0,0 @@ -/* eslint-env jest */ - -require('jest') -const CourtOrderList = require('../src/court_order_list.js') - -let courtOrderListElement -let courtOrderList - -delete window.location -window.location = { reload: jest.fn() } - -beforeEach(() => { - // jest doesn't support window.location like a browser but URL is pretty close - // see https://stackoverflow.com/a/60697570 - window.location = new URL('https://casa-qa.herokuapp.com/casa_cases/CINA-2151') - document.body.innerHTML = '
' - - $(document).ready(() => { - courtOrderListElement = $('#court-orders-list-container') - courtOrderList = new CourtOrderList(courtOrderListElement) - }) -}) - -describe('CourtOrderList constructor', () => { - test('the constructor should be able to extract the resource name from element', (done) => { - $(document).ready(() => { - try { - courtOrderList = new CourtOrderList(courtOrderListElement) - - expect(courtOrderList.resourceName).toBe('casa_case') - done() - } catch (error) { - done(error) - } - }) - }) - - test('the constructor should be able to extract the casa case id from the url', (done) => { - $(document).ready(() => { - try { - const casaCaseId1 = 'CINA-2151' - const casaCaseId2 = 'CINA-1988' - window.location = new URL(`https://casa-qa.herokuapp.com/casa_cases/${casaCaseId1}`) - courtOrderList = new CourtOrderList(courtOrderListElement) - - expect(courtOrderList.casaCaseId).toBe(casaCaseId1) - - window.location = new URL(`https://casa-qa.herokuapp.com/casa_cases/${casaCaseId2}/court_dates/3`) - courtOrderList = new CourtOrderList(courtOrderListElement) - - expect(courtOrderList.casaCaseId).toBe(casaCaseId2) - done() - } catch (error) { - done(error) - } - }) - }) -}) - -describe('addCourtOrder', () => { - test('addCourtOrder should add a textarea and dropdown in a div with class "court-order-entry" as a child of #court-orders-list-container', (done) => { - $(document).ready(() => { - try { - expect(courtOrderListElement.children().length).toBe(0) - - courtOrderList.addCourtOrder() - - expect(courtOrderListElement.children().length).toBe(1) - - const appendedCourtOrder = courtOrderListElement.children().first() - expect(appendedCourtOrder.attr('class')).toContain('court-order-entry') - expect(appendedCourtOrder.find('textarea').length).toBe(1) - expect(appendedCourtOrder.find('select').length).toBe(1) - expect(appendedCourtOrder.find('input[type="hidden"]').length).toBe(1) - done() - } catch (error) { - done(error) - } - }) - }) - - test('addCourtOrder should add elements with attribute values containing the correct indices', (done) => { - $(document).ready(() => { - try { - const courtOrderCount = 5 - - for (let i = 0; i < courtOrderCount; i++) { - courtOrderList.addCourtOrder() - } - - courtOrderListElement.children('div').each(function (index) { - const courtOrderInputs = $(this) - - const textArea = courtOrderInputs.find('textarea') - expect($(textArea).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_text`) - expect($(textArea).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][text]`) - - const select = courtOrderInputs.find('select') - expect($(select).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_implementation_status`) - expect($(select).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][implementation_status]`) - - const hiddenInput = courtOrderInputs.find('input[type="hidden"]') - expect($(hiddenInput).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_casa_case_id`) - expect($(hiddenInput).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][casa_case_id]`) - }) - - courtOrderListElement.children('input').each(function (index) { - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_id`) - }) - - done() - } catch (error) { - done(error) - } - }) - }) -}) - -describe('removeCourtOrder', () => { - const courtOrdersText = ['Ycs(ya$5r^/9QIQG', ')Tf/_T0a%h1q\'N:[', '\\VvOfNZ(xZ~:1Hj?', '"(N=D,s4Q"QcH?F+', '!N0&!J31#LX+d$,k'] - - beforeEach(() => { - $(document).ready(() => { - courtOrderListElement.append($(`\ -
\ - \ - \ - \ -
\ - \ -
\ - \ - \ - \ -
\ - \ -
\ - \ - \ - \ -
\ - \ -
\ - \ - \ - \ -
\ - \ -
\ - \ - \ - \ -
\ - \ - `)) - }) - }) - - test('removeCourtOrder should remove the elements passed to it', (done) => { - $(document).ready(() => { - try { - expect(courtOrderListElement.children().length).toBe(10) - expect($('#casa_case_case_court_orders_attributes_4_text').length).toBe(1) - expect($('#casa_case_case_court_orders_attributes_4_implementation_status').length).toBe(1) - expect($('#casa_case_case_court_orders_attributes_4_id').length).toBe(1) - expect(document.body.innerHTML).toEqual(expect.stringContaining(courtOrdersText[4])) - - courtOrderList.removeCourtOrder($('.court-order-entry').eq(4), $('#casa_case_case_court_orders_attributes_4_id')) - - expect(courtOrderListElement.children().length).toBe(8) - expect($('#casa_case_case_court_orders_attributes_4_text').length).toBe(0) - expect($('#casa_case_case_court_orders_attributes_4_implementation_status').length).toBe(0) - expect($('#casa_case_case_court_orders_attributes_4_id').length).toBe(0) - expect(document.body.innerHTML).toEqual(expect.not.stringContaining(courtOrdersText[4])) - done() - } catch (error) { - done(error) - } - }) - }) - - test('removeCourtOrder should shift the indicies of all the elements after the elements it removes', (done) => { - $(document).ready(() => { - try { - let inputs = courtOrderListElement.children('input') - let textareas = courtOrderListElement.find('textarea') - let selects = courtOrderListElement.find('select') - - expect(inputs.length).toBe(5) - inputs.each(function (index) { - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_id`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][id]`) - }) - - expect(textareas.length).toBe(5) - textareas.each(function (index) { - expect($(this).html()).toBe(courtOrdersText[index]) - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_text`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][text]`) - }) - - expect(selects.length).toBe(5) - selects.each(function (index) { - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_implementation_status`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][implementation_status]`) - }) - - courtOrderList.removeCourtOrder($('.court-order-entry').eq(0), $('#casa_case_case_court_orders_attributes_0_id')) - - expect(document.body.innerHTML).toEqual(expect.not.stringContaining(courtOrdersText[0])) - - inputs = courtOrderListElement.children('input') - textareas = courtOrderListElement.find('textarea') - selects = courtOrderListElement.find('select') - - expect(inputs.length).toBe(4) - inputs.each(function (index) { - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_id`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][id]`) - }) - - expect(textareas.length).toBe(4) - textareas.each(function (index) { - expect($(this).html()).toBe(courtOrdersText[index + 1]) - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_text`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][text]`) - }) - - expect(selects.length).toBe(4) - selects.each(function (index) { - expect($(this).attr('id')).toBe(`casa_case_case_court_orders_attributes_${index}_implementation_status`) - expect($(this).attr('name')).toBe(`casa_case[case_court_orders_attributes][${index}][implementation_status]`) - }) - done() - } catch (error) { - done(error) - } - }) - }) -}) diff --git a/app/javascript/application.js b/app/javascript/application.js index 34435e2854..e513e26c98 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,18 +1,17 @@ /* global window */ - import './jQueryGlobalizer.js' import 'bootstrap' import 'bootstrap-select' import './sweet-alert-confirm.js' +import './controllers' +import 'trix' +import '@rails/actiontext' require('datatables.net-dt')(null, window.jQuery) // First parameter is the global object. Defaults to window if null require('select2')(window.jQuery) - require('@rails/ujs').start() require('@rails/activestorage').start() - require('bootstrap-datepicker') - require('./src/add_additional_expense') require('./src/add_to_calendar_button') require('./src/case_contact') diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000000..c030eb8c7d --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from '@hotwired/stimulus' + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/dismiss_controller.js b/app/javascript/controllers/dismiss_controller.js new file mode 100644 index 0000000000..41a8fb411c --- /dev/null +++ b/app/javascript/controllers/dismiss_controller.js @@ -0,0 +1,13 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = ['element'] + + dismiss (event) { + event.preventDefault() + + const { id } = event.params + document.cookie = `dismiss_${id}=true` + this.elementTarget.classList.add('d-none') + } +} diff --git a/app/javascript/controllers/extended_nested_form_controller.js b/app/javascript/controllers/extended_nested_form_controller.js new file mode 100644 index 0000000000..d8f76545b0 --- /dev/null +++ b/app/javascript/controllers/extended_nested_form_controller.js @@ -0,0 +1,42 @@ +import NestedForm from 'stimulus-rails-nested-form' +import Swal from 'sweetalert2' + +export default class extends NestedForm { + // + remove (e) { + const wrapper = e.target.closest(this.wrapperSelectorValue) + if (wrapper.dataset.newRecord !== 'true' && wrapper.dataset.type === 'COURT_ORDER') { + this.removeCourtOrderWithConfirmation(e, wrapper) + } else { + super.remove(e) + } + } + + removeCourtOrderWithConfirmation (e, wrapper) { + const text = 'Are you sure you want to remove this court order? Doing so will ' + + 'delete all records of it unless it was included in a previous court report.' + Swal.fire({ + icon: 'warning', + title: 'Delete court order?', + text, + showCloseButton: true, + showCancelButton: true, + focusConfirm: false, + + confirmButtonColor: '#d33', + cancelButtonColor: '#39c', + + confirmButtonText: 'Delete', + cancelButtonText: 'Go back' + }).then((result) => { + if (result.isConfirmed) { + this.removeCourtOrder(e, wrapper) + } + }) + } + + removeCourtOrder (e, wrapper) { + super.remove(e) + wrapper.classList.remove('d-flex') + } +} diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000000..64a762ceb6 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + connect () { + console.log('Stimulus is working!') + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000000..d1017ee22c --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,13 @@ +// This file is auto-generated by ./bin/rails stimulus:manifest:update +// Run that command whenever you add a new controller or create them with +// ./bin/rails generate stimulus controllerName + +import { application } from './application' + +import DismissController from './dismiss_controller' +import ExtendedNestedFormController from './extended_nested_form_controller' +import HelloController from './hello_controller' + +application.register('dismiss', DismissController) +application.register('extended-nested-form', ExtendedNestedFormController) +application.register('hello', HelloController) diff --git a/app/javascript/src/casa_case.js b/app/javascript/src/casa_case.js index 00f09b4347..ee32b132f7 100644 --- a/app/javascript/src/casa_case.js +++ b/app/javascript/src/casa_case.js @@ -6,8 +6,6 @@ import Swal from 'sweetalert2' -const CourtOrderList = require('./court_order_list.js') - function copyOrdersFromCaseWithConfirmation () { const id = $(this).next().val() const caseNumber = $('select.siblings-casa-cases').find(':selected').text() @@ -124,8 +122,6 @@ function handleGenerateReport (e) { } $('document').ready(() => { - const courtOrdersListContainer = $('#court-orders-list-container') - $('button.copy-court-button').on('click', copyOrdersFromCaseWithConfirmation) if ($('button.copy-court-button').length) { @@ -140,19 +136,6 @@ $('document').ready(() => { } }) - if (courtOrdersListContainer.length) { - const courtOrders = new CourtOrderList(courtOrdersListContainer) - - $('button#add-court-order-button').on('click', () => { - courtOrders.addCourtOrder() - }) - - $('button.remove-court-order-button').on('click', (event) => { - const orderHTML = $(event.target).parent() - courtOrders.removeCourtOrderWithConfirmation(orderHTML) - }) - } - $('#btnGenerateReport').on('click', handleGenerateReport) if (/\/casa_cases\/.*\?.*success=true/.test(window.location.href)) { diff --git a/app/javascript/src/casa_org.js b/app/javascript/src/casa_org.js index 39a42f3313..d4c2c84125 100644 --- a/app/javascript/src/casa_org.js +++ b/app/javascript/src/casa_org.js @@ -19,15 +19,15 @@ function twilioToggle () { function addCheckedAttr (el) { el.attr('required', true) - el.setAttribute('aria-disabled', false) - el.setAttribute('aria-required', true) + el.attr('aria-disabled', false) + el.attr('aria-required', true) el.removeAttr('disabled') } function removeCheckedAttr (el) { el.removeAttr('required') - el.setAttribute('aria-required', false) - el.setAttribute('aria-disabled', true) + el.attr('aria-required', false) + el.attr('aria-disabled', true) el.attr('disabled', true) } @@ -43,4 +43,5 @@ $('document').ready(() => { } ($('.accordionTwilio').on('click', twilioToggle)) + twilioToggle() }) diff --git a/app/javascript/src/court_order_list.js b/app/javascript/src/court_order_list.js deleted file mode 100644 index 499d238e4e..0000000000 --- a/app/javascript/src/court_order_list.js +++ /dev/null @@ -1,131 +0,0 @@ -import Swal from 'sweetalert2' - -// Replaces a number in a string with its value -1 -// @param {string} str The string containing the number to replace -// @param {number} num The number to replace -// @return {string} The new string with the number decremented -function replaceNumberWithDecrement (str, num) { - const captureStringWithoutNumPattern = new RegExp(`(^.*)${num}(.*$)`) - const stringWithoutNum = str.match(captureStringWithoutNumPattern) - - return stringWithoutNum[1] + (num - 1) + stringWithoutNum[2] -} - -module.exports = class CourtOrderList { - // @param {object} The HTMLElement to contain the list items - constructor (courtOrdersWidget) { - // The following regex is intended for pathnames such as "/casa_cases/CINA-19-1004/court_dates/new" - const urlMatch = window.location.pathname.match(/^\/([a-z_]+)s\/(\w+-+\d+)(\/(([a-z_]+)s))?/).filter(match => match !== undefined) - this.courtOrdersWidget = courtOrdersWidget - this.resourceName = this.courtOrdersWidget[0].dataset.resource - // The casaCaseId will be something like "CINA-19-1004" - this.casaCaseId = urlMatch[2] - } - - // Adds a row containing a text field to write the court order and a dropdown to specify the order status - addCourtOrder () { - const index = this.courtOrdersWidget.children('.court-order-entry').length - const resourceName = this.resourceName - const courtOrderRow = $(`\ -
\ - - \ - - - - - - -
`) - - this.courtOrdersWidget.append(courtOrderRow) - courtOrderRow.children('textarea').trigger('focus') - } - - // Removes a row of elements representing a single court order - // and removes the accompanying hidden input containing the order id - // @param {object} order The jQuery object representing the court order div to remove - // @param {object} orderHiddenIdInput The jQuery object representing the hidden court order id input - removeCourtOrder (order, orderHiddenIdInput) { - // Index relative to the other court orders excluding hidden inputs - const index = order.index() / 2 - - order.remove() - orderHiddenIdInput.remove() - - // Decrement indicies of all siblings after deleted element - this.courtOrdersWidget.children(`.court-order-entry:nth-child(n+${2 * index})`).each(function (originalSiblingIndex) { - const courtOrderSibling = $(this) - const courtOrderSiblingSelect = courtOrderSibling.children('select') - const courtOrderSiblingTextArea = courtOrderSibling.children('textarea') - - courtOrderSiblingSelect.attr('id', replaceNumberWithDecrement(courtOrderSiblingSelect.attr('id'), originalSiblingIndex + index + 1)) - courtOrderSiblingSelect.attr('name', replaceNumberWithDecrement(courtOrderSiblingSelect.attr('name'), originalSiblingIndex + index + 1)) - courtOrderSiblingTextArea.attr('id', replaceNumberWithDecrement(courtOrderSiblingTextArea.attr('id'), originalSiblingIndex + index + 1)) - courtOrderSiblingTextArea.attr('name', replaceNumberWithDecrement(courtOrderSiblingTextArea.attr('name'), originalSiblingIndex + index + 1)) - }) - - this.courtOrdersWidget.children(`input[type="hidden"]:nth-child(n+${2 * (index + 1)})`).each(function (originalSiblingIndex) { - const courtOrderSiblingId = $(this) - - courtOrderSiblingId.attr('id', replaceNumberWithDecrement(courtOrderSiblingId.attr('id'), originalSiblingIndex + index + 1)) - courtOrderSiblingId.attr('name', replaceNumberWithDecrement(courtOrderSiblingId.attr('name'), originalSiblingIndex + index + 1)) - }) - } - - removeCourtOrderWithConfirmation (order) { - const text = 'Are you sure you want to remove this court order? Doing so will ' + - 'delete all records of it unless it was included in a previous court report.' - Swal.fire({ - icon: 'warning', - title: 'Delete court order?', - text, - showCloseButton: true, - showCancelButton: true, - focusConfirm: false, - - confirmButtonColor: '#d33', - cancelButtonColor: '#39c', - - confirmButtonText: 'Delete', - cancelButtonText: 'Go back' - }).then((result) => { - if (result.isConfirmed) { - this.removeCourtOrderAction(order) - } - }) - } - - removeCourtOrderAction (order) { - const orderHiddenIdInput = order.next('input[type="hidden"]') - - $.ajax({ - url: `/case_court_orders/${orderHiddenIdInput.val()}`, - method: 'delete', - success: () => { - this.removeCourtOrder(order, orderHiddenIdInput) - Swal.fire({ - icon: 'success', - text: 'Court order has been removed.', - showCloseButton: true - }) - }, - error: () => { - Swal.fire({ - icon: 'error', - text: 'Something went wrong when attempting to delete this court order.', - showCloseButton: true - }) - } - }) - } -} diff --git a/app/javascript/src/dashboard.js b/app/javascript/src/dashboard.js index 32282d6c02..c3877066c6 100644 --- a/app/javascript/src/dashboard.js +++ b/app/javascript/src/dashboard.js @@ -90,7 +90,39 @@ $('document').ready(() => { const casaCasePath = id => `/casa_cases/${id}` const volunteersTable = $('table#volunteers').DataTable({ autoWidth: false, - stateSave: false, + stateSave: true, + initComplete: function (settings, json) { + this.api().columns().every(function (index) { + const columnVisible = this.visible() + return $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) + }) + }, + stateSaveCallback: function (settings, data) { + $.ajax({ + url: '/preference_sets/table_state_update/' + settings.nTable.id + '_table', + + data: { + table_state: JSON.stringify(data) + }, + dataType: 'json', + type: 'POST', + success: function (response) { } + }) + }, + stateSaveParams: function (settings, data) { + data.search.search = '' + return data + }, + stateLoadCallback: function (settings, callback) { + $.ajax({ + url: '/preference_sets/table_state/' + settings.nTable.id + '_table', + dataType: 'json', + type: 'GET', + success: function (json) { + callback(json) + } + }) + }, order: [[6, 'desc']], columns: [ { @@ -106,8 +138,7 @@ $('document').ready(() => { }, { name: 'email', - render: (data, type, row, meta) => row.email, - visible: false + render: (data, type, row, meta) => row.email }, { className: 'supervisor-column', @@ -170,8 +201,7 @@ $('document').ready(() => { ` : 'None ❌' }, - searchable: false, - visible: true + searchable: false }, { name: 'contacts_made_in_past_days', @@ -181,8 +211,7 @@ $('document').ready(() => { ${row.contacts_made_in_past_days} ` }, - searchable: false, - visible: false + searchable: false }, { name: 'hours_spent_in_days', @@ -200,20 +229,19 @@ $('document').ready(() => { const languages = row.extra_languages.map(x => x.name).join(', ') return row.extra_languages.length > 0 ? `🌎` : '' }, - searchable: false, - visible: true + searchable: false }, { name: 'actions', orderable: false, render: (data, type, row, meta) => { return ` - Actions - - Edit + Actions + + Edit - - Impersonate + + Impersonate ` }, @@ -258,13 +286,7 @@ $('document').ready(() => { // columns are visible volunteersTable.columns().every(function (index) { const columnVisible = this.visible() - - if (columnVisible) { - $('#visibleColumns input[data-column="' + index + '"]').prop('checked', true) - } else { - $('#visibleColumns input[data-column="' + index + '"]').prop('checked', false) - } - + $('#visibleColumns input[data-column="' + index + '"]').prop('checked', columnVisible) return true }) diff --git a/app/javascript/src/display_app_metric.js b/app/javascript/src/display_app_metric.js index a944c14de0..13bcbfd5f7 100644 --- a/app/javascript/src/display_app_metric.js +++ b/app/javascript/src/display_app_metric.js @@ -6,18 +6,30 @@ Chart.register(...registerables) const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] $(document).ready(function () { - $.ajax({ - type: 'GET', - url: '/health/case_contacts_creation_times_in_last_week', - success: function (data) { - const timestamps = data.timestamps - - const counts = getCountsByDayAndHour(timestamps) - const dataset = getDatasetFromCounts(counts) - - createChart(dataset) - } - }) + const chartElement = document.getElementById('myChart') + + if (chartElement) { + $.ajax({ + type: 'GET', + url: '/health/case_contacts_creation_times_in_last_week', + success: function (data) { + const timestamps = data.timestamps + const counts = getCountsByDayAndHour(timestamps) + const dataset = getDatasetFromCounts(counts) + + createChart(chartElement, dataset) + }, + error: function (xhr, status, error) { + console.error('Failed to fetch data for case contact entry times chart display') + console.error(error) + $('#chart-error-message').append(` + `) + $('.text-center').hide() + } + }) + } }) function getCountsByDayAndHour (timestamps) { @@ -54,18 +66,20 @@ function getDatasetFromCounts (counts) { return dataset } -function createChart (dataset) { - const ctx = document.getElementById('myChart').getContext('2d') - // eslint-disable-next-line no-unused-vars - const myChart = new Chart(ctx, { +function createChart (chartElement, dataset) { + const ctx = chartElement.getContext('2d') + + return new Chart(ctx, { type: 'bubble', data: { - datasets: [{ - label: 'Case Contacts Creation Times in Last Week', - data: dataset, - backgroundColor: 'rgba(255, 99, 132, 0.2)', - borderColor: 'rgba(255, 99, 132, 1)' - }] + datasets: [ + { + label: 'Case Contacts Creation Times in Last Week', + data: dataset, + backgroundColor: 'rgba(255, 99, 132, 0.2)', + borderColor: 'rgba(255, 99, 132, 1)' + } + ] }, options: getChartOptions() }) diff --git a/app/javascript/src/plainadmin.js b/app/javascript/src/plainadmin.js index d0dd3a0fc5..a91f337bb2 100644 --- a/app/javascript/src/plainadmin.js +++ b/app/javascript/src/plainadmin.js @@ -28,7 +28,7 @@ } }) } - overlay.addEventListener('click', () => { + overlay?.addEventListener('click', () => { sidebarNavWrapper.classList.remove('active') overlay.classList.remove('active') mainWrapper.classList.remove('active') diff --git a/app/models/banner.rb b/app/models/banner.rb new file mode 100644 index 0000000000..dc79e398f2 --- /dev/null +++ b/app/models/banner.rb @@ -0,0 +1,43 @@ +class Banner < ApplicationRecord + belongs_to :casa_org + belongs_to :user + has_rich_text :content + + scope :active, -> { where(active: true) } + + validates_presence_of :name + validate :only_one_banner_is_active_per_organization + + private + + def only_one_banner_is_active_per_organization + is_other_banner_active = casa_org.banners.where.not(id: id).any?(&:active?) + more_than_one_banner_active = is_other_banner_active && active? + if more_than_one_banner_active + errors.add(:base, "Only one banner can be active at a time. Mark the other banners as not active before marking this banner as active.") + end + end +end + +# == Schema Information +# +# Table name: banners +# +# id :bigint not null, primary key +# active :boolean default(FALSE) +# name :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# user_id :bigint not null +# +# Indexes +# +# index_banners_on_casa_org_id (casa_org_id) +# index_banners_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (casa_org_id => casa_orgs.id) +# fk_rails_... (user_id => users.id) +# diff --git a/app/models/casa_case.rb b/app/models/casa_case.rb index ef28ef4862..bc1e94a652 100644 --- a/app/models/casa_case.rb +++ b/app/models/casa_case.rb @@ -3,7 +3,7 @@ class CasaCase < ApplicationRecord include DateHelper extend FriendlyId - self.ignored_columns = %w[hearing_type_id judge_id transition_aged_youth court_report_due_date] + self.ignored_columns = %w[transition_aged_youth court_report_due_date] attr_accessor :validate_contact_type @@ -34,6 +34,8 @@ class CasaCase < ApplicationRecord has_many :emancipation_options, through: :casa_cases_emancipation_options has_many :court_dates, dependent: :destroy has_many :placements, dependent: :destroy + has_many :case_group_memberships + has_many :case_groups, through: :case_group_memberships has_many_attached :court_reports validates :case_number, uniqueness: {scope: :casa_org_id, case_sensitive: false}, presence: true @@ -49,7 +51,7 @@ class CasaCase < ApplicationRecord accepts_nested_attributes_for :volunteers has_many :case_court_orders, -> { order "id asc" }, dependent: :destroy - accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank + accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank, allow_destroy: true enum court_report_status: {not_submitted: 0, submitted: 1, in_review: 2, completed: 3}, _prefix: :court_report @@ -234,8 +236,6 @@ def should_generate_new_friendly_id? # # index_casa_cases_on_casa_org_id (casa_org_id) # index_casa_cases_on_case_number_and_casa_org_id (case_number,casa_org_id) UNIQUE -# index_casa_cases_on_hearing_type_id (hearing_type_id) -# index_casa_cases_on_judge_id (judge_id) # index_casa_cases_on_slug (slug) # # Foreign Keys diff --git a/app/models/casa_org.rb b/app/models/casa_org.rb index 9c56efde5f..414068fdcf 100644 --- a/app/models/casa_org.rb +++ b/app/models/casa_org.rb @@ -20,6 +20,9 @@ class CasaOrg < ApplicationRecord has_many :case_assignments, through: :users, source: :casa_cases has_many :languages, dependent: :destroy has_many :placements, through: :casa_cases + has_many :banners, dependent: :destroy + has_many :learning_hour_types, dependent: :destroy + has_many :case_groups, dependent: :destroy has_one_attached :logo has_one_attached :court_report_template diff --git a/app/models/case_assignment.rb b/app/models/case_assignment.rb index a9a7a918bf..5941198ee2 100644 --- a/app/models/case_assignment.rb +++ b/app/models/case_assignment.rb @@ -31,13 +31,14 @@ def casa_case_and_volunteer_must_belong_to_same_casa_org # # Table name: case_assignments # -# id :bigint not null, primary key -# active :boolean default(TRUE), not null -# hide_old_contacts :boolean default(FALSE) -# created_at :datetime not null -# updated_at :datetime not null -# casa_case_id :bigint not null -# volunteer_id :bigint not null +# id :bigint not null, primary key +# active :boolean default(TRUE), not null +# allow_reimbursement :boolean default(TRUE) +# hide_old_contacts :boolean default(FALSE) +# created_at :datetime not null +# updated_at :datetime not null +# casa_case_id :bigint not null +# volunteer_id :bigint not null # # Indexes # diff --git a/app/models/case_court_report_context.rb b/app/models/case_court_report_context.rb index 32ee0b04fc..5bb295fcc8 100644 --- a/app/models/case_court_report_context.rb +++ b/app/models/case_court_report_context.rb @@ -10,6 +10,8 @@ def initialize(args = {}) @volunteer = Volunteer.find(args[:volunteer_id]) if args[:volunteer_id] @time_zone = args[:time_zone] @path_to_template = args[:path_to_template] + @court_date = args[:court_date] || @casa_case.next_court_date + @case_court_orders = args[:case_court_orders] || @casa_case.case_court_orders end def context @@ -29,7 +31,8 @@ def prepare_context(is_default_template) case_mandates: prepare_case_orders, # backwards compatible with old Montgomery template - keep this! TODO test full generation latest_hearing_date: latest_hearing_date.nil? ? "_______" : I18n.l(latest_hearing_date.date, format: :full, default: nil), org_address: org_address(is_default_template), - volunteer: volunteer_info + volunteer: volunteer_info, + hearing_type_name: @court_date&.hearing_type&.name || "None" } end @@ -42,16 +45,12 @@ def prepare_case_contacts end def prepare_case_orders - case_order_data = [] - - @casa_case.case_court_orders.each do |case_order| - case_order_data << { + @case_court_orders.map do |case_order| + { order: case_order.text, status: case_order.implementation_status&.humanize } end - - case_order_data end def filter_out_old_case_contacts(interviewees) @@ -65,11 +64,11 @@ def filter_out_old_case_contacts(interviewees) def prepare_case_details { - court_date: I18n.l(@casa_case.next_court_date&.date, format: :full, default: nil), + court_date: I18n.l(@court_date&.date, format: :full, default: nil), case_number: @casa_case.case_number, dob: I18n.l(@casa_case.birth_month_year_youth, format: :youth_date_of_birth, default: nil), is_transitioning: @casa_case.in_transition_age?, - judge_name: @casa_case.next_court_date&.judge&.name + judge_name: @court_date&.judge&.name } end diff --git a/app/models/case_group.rb b/app/models/case_group.rb new file mode 100644 index 0000000000..2f6535a197 --- /dev/null +++ b/app/models/case_group.rb @@ -0,0 +1,26 @@ +class CaseGroup < ApplicationRecord + belongs_to :casa_org + has_many :case_group_memberships + has_many :casa_cases, through: :case_group_memberships + + validates_presence_of :name, :case_group_memberships +end + +# == Schema Information +# +# Table name: case_groups +# +# id :bigint not null, primary key +# name :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# +# Indexes +# +# index_case_groups_on_casa_org_id (casa_org_id) +# +# Foreign Keys +# +# fk_rails_... (casa_org_id => casa_orgs.id) +# diff --git a/app/models/case_group_membership.rb b/app/models/case_group_membership.rb new file mode 100644 index 0000000000..2022aba509 --- /dev/null +++ b/app/models/case_group_membership.rb @@ -0,0 +1,26 @@ +class CaseGroupMembership < ApplicationRecord + belongs_to :case_group + belongs_to :casa_case + has_one :casa_org, through: :case_group +end + +# == Schema Information +# +# Table name: case_group_memberships +# +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# casa_case_id :bigint not null +# case_group_id :bigint not null +# +# Indexes +# +# index_case_group_memberships_on_casa_case_id (casa_case_id) +# index_case_group_memberships_on_case_group_id (case_group_id) +# +# Foreign Keys +# +# fk_rails_... (casa_case_id => casa_cases.id) +# fk_rails_... (case_group_id => case_groups.id) +# diff --git a/app/models/court_date.rb b/app/models/court_date.rb index e896d2da79..bdaa020738 100644 --- a/app/models/court_date.rb +++ b/app/models/court_date.rb @@ -10,11 +10,11 @@ class CourtDate < ApplicationRecord belongs_to :hearing_type, optional: true belongs_to :judge, optional: true - accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank + accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank, allow_destroy: true - scope :ordered_ascending, -> { order("date asc") } + before_save :set_court_report_due_date - DOCX_TEMPLATE_PATH = Rails.root.join("app", "documents", "templates", "default_past_court_date_template.docx") + scope :ordered_ascending, -> { order("date asc") } # get reports associated with the case this belongs to before this court date but after the court date before this one def associated_reports @@ -34,34 +34,15 @@ def additional_info? case_court_orders.any? || hearing_type || judge end - def generate_report - template = Sablon.template(File.expand_path(DOCX_TEMPLATE_PATH)) - - template.render_to_string(context_hash) - end - def display_name "#{casa_case.case_number} - Court Date - #{I18n.l(date.to_date)}" end private - def context_hash - { - court_date: date, - case_number: casa_case.case_number, - judge_name: judge&.name || "None", - hearing_type_name: hearing_type&.name || "None", - case_court_orders: case_court_orders_context_hash - } - end - - def case_court_orders_context_hash - case_court_orders.map do |order| - { - text: order.text, - implementation_status: order.implementation_status&.humanize - } + def set_court_report_due_date + if date.present? && court_report_due_date.blank? + self.court_report_due_date = date - 3.weeks end end end diff --git a/app/models/learning_hour.rb b/app/models/learning_hour.rb index 6ca281858a..8967657267 100644 --- a/app/models/learning_hour.rb +++ b/app/models/learning_hour.rb @@ -1,15 +1,7 @@ class LearningHour < ApplicationRecord belongs_to :user + belongs_to :learning_hour_type - enum learning_type: { - book: 1, - movie: 2, - webinar: 3, - conference: 4, - other: 5 - } - - validates :learning_type, presence: true validates :duration_minutes, presence: true validates :duration_minutes, numericality: {greater_than: 0}, if: :zero_duration_hours? validates :name, presence: {message: "/ Title cannot be blank"} @@ -35,21 +27,23 @@ def occurred_at_not_in_future # # Table name: learning_hours # -# id :bigint not null, primary key -# duration_hours :integer not null -# duration_minutes :integer not null -# learning_type :integer default("other") -# name :string not null -# occurred_at :datetime not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# duration_hours :integer not null +# duration_minutes :integer not null +# name :string not null +# occurred_at :datetime not null +# created_at :datetime not null +# updated_at :datetime not null +# learning_hour_type_id :bigint +# user_id :bigint not null # # Indexes # -# index_learning_hours_on_user_id (user_id) +# index_learning_hours_on_learning_hour_type_id (learning_hour_type_id) +# index_learning_hours_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (learning_hour_type_id => learning_hour_types.id) # fk_rails_... (user_id => users.id) # diff --git a/app/models/learning_hour_type.rb b/app/models/learning_hour_type.rb new file mode 100644 index 0000000000..17c4f11ebf --- /dev/null +++ b/app/models/learning_hour_type.rb @@ -0,0 +1,36 @@ +class LearningHourType < ApplicationRecord + belongs_to :casa_org + + validates :name, presence: true, uniqueness: {scope: %i[casa_org], case_sensitive: false} + before_validation :strip_name + default_scope { order(position: :asc, name: :asc) } + scope :for_organization, ->(org) { where(casa_org: org).order(:name) } + scope :active, -> { where(active: true) } + + private + + def strip_name + self.name = name.strip if name + end +end + +# == Schema Information +# +# Table name: learning_hour_types +# +# id :bigint not null, primary key +# active :boolean default(TRUE) +# name :string +# position :integer default(1) +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# +# Indexes +# +# index_learning_hour_types_on_casa_org_id (casa_org_id) +# +# Foreign Keys +# +# fk_rails_... (casa_org_id => casa_orgs.id) +# diff --git a/app/models/learning_hours_report.rb b/app/models/learning_hours_report.rb index af69e360b0..b0816a3c07 100644 --- a/app/models/learning_hours_report.rb +++ b/app/models/learning_hours_report.rb @@ -4,6 +4,7 @@ class LearningHoursReport def initialize(casa_org_id) @learning_hours = LearningHour.includes(:user) .where(user: {casa_org_id: casa_org_id}) + .order(:id) end def to_csv diff --git a/app/models/note.rb b/app/models/note.rb index 0e97a4059a..583993528f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -19,3 +19,7 @@ class Note < ApplicationRecord # # index_notes_on_notable (notable_type,notable_id) # +# Foreign Keys +# +# fk_rails_... (creator_id => users.id) +# diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index f4cc0cc98f..a274251fe2 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -143,4 +143,5 @@ def current_organization alias_method :modify_organization?, :is_admin? alias_method :see_import_page?, :is_admin? + alias_method :see_banner_page?, :admin_or_supervisor? end diff --git a/app/policies/casa_case_policy.rb b/app/policies/casa_case_policy.rb index 648b51d0cf..251f5c0cf6 100644 --- a/app/policies/casa_case_policy.rb +++ b/app/policies/casa_case_policy.rb @@ -117,6 +117,6 @@ def is_volunteer_actively_assigned_to_case? end def case_court_orders_attributes - {case_court_orders_attributes: %i[text implementation_status id]} + {case_court_orders_attributes: %i[text implementation_status id _destroy]} end end diff --git a/app/policies/case_assignment_policy.rb b/app/policies/case_assignment_policy.rb index d7558a6e19..75f74ee734 100644 --- a/app/policies/case_assignment_policy.rb +++ b/app/policies/case_assignment_policy.rb @@ -24,6 +24,10 @@ def unassign? record.active? && admin_or_supervisor? && same_org? end + def reimbursement? + admin_or_supervisor? && same_org? + end + def hide_contacts? !record.active? && !record.hide_old_contacts? && admin_or_supervisor? end diff --git a/app/policies/learning_hour_type_policy.rb b/app/policies/learning_hour_type_policy.rb new file mode 100644 index 0000000000..3843a2ac46 --- /dev/null +++ b/app/policies/learning_hour_type_policy.rb @@ -0,0 +1,2 @@ +class LearningHourTypePolicy < ApplicationPolicy +end diff --git a/app/services/learning_hours_export_csv_service.rb b/app/services/learning_hours_export_csv_service.rb index f17ba7ebc1..378faeb50b 100644 --- a/app/services/learning_hours_export_csv_service.rb +++ b/app/services/learning_hours_export_csv_service.rb @@ -27,7 +27,7 @@ def filtered_learning_hours(learning_hour = nil) { volunteer_name: learning_hour&.user&.display_name, learning_hours_title: learning_hour&.name, - learning_hours_type: learning_hour&.learning_type, + learning_hours_type: learning_hour&.learning_hour_type&.name, duration: get_duration(learning_hour), date_of_learning: learning_hour&.occurred_at&.strftime("%F") } diff --git a/app/services/preference_set_table_state_service.rb b/app/services/preference_set_table_state_service.rb new file mode 100644 index 0000000000..25fca84f13 --- /dev/null +++ b/app/services/preference_set_table_state_service.rb @@ -0,0 +1,25 @@ +class PreferenceSetTableStateService + class TableStateUpdateFailed < StandardError; end + + def initialize(user_id:) + @user_id = user_id + end + + def table_state(table_name:) + preference_set.table_state[table_name] + end + + def update!(table_name:, table_state:) + preference_set.table_state[table_name] = table_state + preference_set.save! + rescue + raise TableStateUpdateFailed, "Failed to update table state for '#{table_name}'" + end + + private + + attr_reader :user_id + def preference_set + @preference_set ||= PreferenceSet.find_by!(user_id: user_id) + end +end diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 0000000000..49ba357dd1 --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
diff --git a/app/views/all_casa_admins/casa_admins/edit.html.erb b/app/views/all_casa_admins/casa_admins/edit.html.erb index f4a216b8fd..924143f672 100644 --- a/app/views/all_casa_admins/casa_admins/edit.html.erb +++ b/app/views/all_casa_admins/casa_admins/edit.html.erb @@ -1,6 +1,6 @@
-

Edit Admin

+

Edit Admin

<%= render 'layouts/flash_messages' %> diff --git a/app/views/all_casa_admins/casa_admins/new.html.erb b/app/views/all_casa_admins/casa_admins/new.html.erb index 6b07fd819f..e46c5476a4 100644 --- a/app/views/all_casa_admins/casa_admins/new.html.erb +++ b/app/views/all_casa_admins/casa_admins/new.html.erb @@ -1,6 +1,6 @@
-

New CASA Admin for <%= selected_organization.name %>

+

New CASA Admin for <%= selected_organization.name %>

diff --git a/app/views/all_casa_admins/casa_orgs/new.html.erb b/app/views/all_casa_admins/casa_orgs/new.html.erb index 4377626031..9e0ee4ed66 100644 --- a/app/views/all_casa_admins/casa_orgs/new.html.erb +++ b/app/views/all_casa_admins/casa_orgs/new.html.erb @@ -1,6 +1,6 @@
-

Create a new CASA Organization

+

Create a new CASA Organization

diff --git a/app/views/all_casa_admins/casa_orgs/show.html.erb b/app/views/all_casa_admins/casa_orgs/show.html.erb index f4331aeae1..bd742a86b1 100644 --- a/app/views/all_casa_admins/casa_orgs/show.html.erb +++ b/app/views/all_casa_admins/casa_orgs/show.html.erb @@ -2,7 +2,7 @@
-

<%= selected_organization.name %>

+

<%= selected_organization.name %>

diff --git a/app/views/all_casa_admins/dashboard/show.html.erb b/app/views/all_casa_admins/dashboard/show.html.erb index a1dfab81c1..a7630d0f99 100644 --- a/app/views/all_casa_admins/dashboard/show.html.erb +++ b/app/views/all_casa_admins/dashboard/show.html.erb @@ -2,7 +2,7 @@
-

All CASA Admin

+

All CASA Admin

<%= render "layouts/flash_messages" %> diff --git a/app/views/all_casa_admins/edit.html.erb b/app/views/all_casa_admins/edit.html.erb index ffd4a58ef6..13936719ec 100644 --- a/app/views/all_casa_admins/edit.html.erb +++ b/app/views/all_casa_admins/edit.html.erb @@ -1,6 +1,6 @@
-

Edit Profile

+

Edit Profile

@@ -18,8 +18,8 @@
- <%= form.submit "Update Profile", class: "main-btn primary-btn btn-hover" %> -
diff --git a/app/views/all_casa_admins/new.html.erb b/app/views/all_casa_admins/new.html.erb index b79e6c61ac..b78084f7d2 100644 --- a/app/views/all_casa_admins/new.html.erb +++ b/app/views/all_casa_admins/new.html.erb @@ -1,6 +1,6 @@
-

New All CASA Admin

+

New All CASA Admin

diff --git a/app/views/all_casa_admins/passwords/new.html.erb b/app/views/all_casa_admins/passwords/new.html.erb index a32cde4d4c..c51365bb92 100644 --- a/app/views/all_casa_admins/passwords/new.html.erb +++ b/app/views/all_casa_admins/passwords/new.html.erb @@ -4,7 +4,7 @@

-

Forgot your password?

+

Forgot your password?


<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: {method: :post}) do |f| %> diff --git a/app/views/all_casa_admins/patch_notes/index.html.erb b/app/views/all_casa_admins/patch_notes/index.html.erb index a9f8347479..8c1230d17a 100644 --- a/app/views/all_casa_admins/patch_notes/index.html.erb +++ b/app/views/all_casa_admins/patch_notes/index.html.erb @@ -4,7 +4,7 @@
-

Patch Notes

+

Patch Notes

diff --git a/app/views/banners/_form.html.erb b/app/views/banners/_form.html.erb new file mode 100644 index 0000000000..000eacbc4b --- /dev/null +++ b/app/views/banners/_form.html.erb @@ -0,0 +1,38 @@ +
+
+
+
+

+ <% if banner.persisted? %> + Edit Banner + <% else %> + New Banner + <% end %> +

+
+
+
+
+
+ <%= form_with model: banner do |form| %> + <%= render "shared/error_messages", resource: banner %> + +
+ <%= form.label :name %> + <%= form.text_field :name, required: true %> +
+
+ <%= form.check_box :active, class: 'form-check-input' %> + <%= form.label :active, "Active?", class: 'form-check-label' %> +
+
+ <%= form.label :content %> + <%= form.rich_text_area :content %> +
+
+ <%= button_tag(type: "submit", class: "btn-sm main-btn primary-btn btn-hover") do %> + Submit + <% end %> +
+ <% end %> +
diff --git a/app/views/banners/edit.html.erb b/app/views/banners/edit.html.erb new file mode 100644 index 0000000000..bed703e40e --- /dev/null +++ b/app/views/banners/edit.html.erb @@ -0,0 +1 @@ +<%= render 'banners/form', banner: @banner %> diff --git a/app/views/banners/index.html.erb b/app/views/banners/index.html.erb new file mode 100644 index 0000000000..b0e2edb0f9 --- /dev/null +++ b/app/views/banners/index.html.erb @@ -0,0 +1,70 @@ +
+
+
+
+

Banners

+
+
+
+
+
+
+
+
+

Banners

+

+ Banners display at the top of the page and can be used to callout a message to volunteers. One example is displaying a banner with a link to a survey that volunteers can fill out. You can create multiple banners but only one can be active at a time. +

+ <%= link_to new_banner_path, class: "btn-sm main-btn primary-btn btn-hover" do %> + + New Banner + <% end %> +
+ + + + + + + + + + + + <% @banners.each do |banner| %> + + + + + + + + <% end %> + +
NameActive?Last Updated ByUpdated AtActions
+ <%= banner.name %> + + <% if banner.active? %> + Yes + <% else %> + No + <% end %> + + <%= banner.user.display_name %> + + <%= time_ago_in_words(banner.updated_at) %> ago + + <%= link_to edit_banner_path(banner) do %> +
+ +
+ <% end %> + <%= link_to 'Delete', banner_path(banner), class: 'btn btn-danger', method: :delete, data: { confirm: "Are you sure that you want to delete this banner?" } %> +
+
+
+
+
+
diff --git a/app/views/banners/new.html.erb b/app/views/banners/new.html.erb new file mode 100644 index 0000000000..bed703e40e --- /dev/null +++ b/app/views/banners/new.html.erb @@ -0,0 +1 @@ +<%= render 'banners/form', banner: @banner %> diff --git a/app/views/bulk_court_dates/new.html.erb b/app/views/bulk_court_dates/new.html.erb new file mode 100644 index 0000000000..8d90f16ba8 --- /dev/null +++ b/app/views/bulk_court_dates/new.html.erb @@ -0,0 +1,53 @@ +
+
+
+
+

New Bulk Court Date

+
+
+
+
+
+ <%= form_with(model: @court_date, url: bulk_court_dates_path, method: :post, local: true, data: { controller: "extended-nested-form", nested_form_wrapper_selector_value: ".nested-form-wrapper" }) do |form| %> + <%= render "/shared/error_messages", resource: @court_date %> +
+
+
Create a court date for all cases in a group
+
+
+ +
+
+
+
+ <%= form.label :case_group_id, "Case Group" %> +
+ <%= form.collection_select( + :case_group_id, + current_organization.case_groups, + :id, :name, + {include_hidden: false, include_blank: "-Select Case Group-"}, + {class: "form-control"} + ) %> +
+
+ + <%= render 'court_dates/fields', court_date: @court_date, form: form, casa_case: nil %> +
+ <% end %> +
diff --git a/app/views/casa_admins/_form.html.erb b/app/views/casa_admins/_form.html.erb index e78f6c2ec6..59fdde9c4c 100644 --- a/app/views/casa_admins/_form.html.erb +++ b/app/views/casa_admins/_form.html.erb @@ -2,7 +2,7 @@
-

<%= title %>

+

<%= title %>

diff --git a/app/views/casa_admins/index.html.erb b/app/views/casa_admins/index.html.erb index 26588e8f04..0ff1e8ae65 100644 --- a/app/views/casa_admins/index.html.erb +++ b/app/views/casa_admins/index.html.erb @@ -2,7 +2,7 @@
-

Casa Admins

+

Casa Admins

@@ -45,7 +45,7 @@ - <%= link_to edit_casa_admin_path(admin), class: "text-danger h5" do %> + <%= link_to edit_casa_admin_path(admin), class: "text-danger" do %> Edit <% end %> diff --git a/app/views/casa_cases/_form.html.erb b/app/views/casa_cases/_form.html.erb index 5da0033ac4..c24bae6e82 100644 --- a/app/views/casa_cases/_form.html.erb +++ b/app/views/casa_cases/_form.html.erb @@ -1,6 +1,7 @@
- <%= form_with(model: casa_case, local: true) do |form| %> + <%= form_with(model: casa_case, local: true, + data: { controller: "extended-nested-form", nested_form_wrapper_selector_value: ".nested-form-wrapper" }) do |form| %> <%= render "/shared/error_messages", resource: casa_case %> <% if casa_case.active %>
diff --git a/app/views/casa_cases/_volunteer_assignment.html.erb b/app/views/casa_cases/_volunteer_assignment.html.erb index f48836e60f..b18255973a 100644 --- a/app/views/casa_cases/_volunteer_assignment.html.erb +++ b/app/views/casa_cases/_volunteer_assignment.html.erb @@ -32,6 +32,14 @@ <%= I18n.l(assignment.updated_at, format: :full, default: nil) %> <% end %> + +
+ <%= form_with model: assignment, url: reimbursement_case_assignment_path(assignment), method: :patch do |form| %> + <%= form.check_box :allow_reimbursement, { class: "form-check-input", role: "switch", onchange: "this.form.submit();" } %> + <% end %> +
+ + <% if policy(assignment).unassign? %> <%= button_to("Unassign Volunteer", diff --git a/app/views/casa_cases/index.html.erb b/app/views/casa_cases/index.html.erb index 422a8a21eb..85b4fc0667 100644 --- a/app/views/casa_cases/index.html.erb +++ b/app/views/casa_cases/index.html.erb @@ -1,11 +1,16 @@ +<% if Rails.env.development? %> +
+
+<% end %> +
<% if current_user.volunteer? %> -

My <%= "Case".pluralize(@casa_cases.count) %>

+

My <%= "Case".pluralize(@casa_cases.count) %>

<% else %> -

<%= "Case".pluralize(@casa_cases.count) %>

+

<%= "Case".pluralize(@casa_cases.count) %>

<% end %>
@@ -15,11 +20,24 @@ <% if policy(:dashboard).see_volunteers_section? %> <%= link_to new_casa_case_path, class: "main-btn btn-sm primary-btn btn-hover ml-3" do %> - - New Case + + New Case <% end %> <% end %> + <% if policy(:application).admin_or_supervisor? %> + + <%= link_to case_groups_path, class: "main-btn btn-sm primary-btn btn-hover ml-3" do %> + Case Groups + <% end %> + + + <%= link_to new_bulk_court_date_path, class: "main-btn btn-sm primary-btn btn-hover ml-3" do %> + + New Bulk Court Date + <% end %> + + <% end %> <% if policy(CasaCase).can_see_filters? %>
<%= form.label :court_report_template, "Court report template" %> - <%= form.file_field :court_report_template, class: "form-control" %> +
+
+ <%= form.file_field :court_report_template, class: "form-control" %> +
+
+ <% if current_organization.court_report_template.attached? %> + <%= link_to 'Download Current Template', rails_blob_path(current_organization.court_report_template, only_path: true), class: "btn btn-info" %> + <% end %> +
+

Organization Features

@@ -116,3 +123,17 @@ <%= render "judges" %> <%= render "sent_emails" %>
+
+
+
+
+

+ Manage Learning Hours +

+
+
+
+
+
+ <%= render "learning_hour_types" %> +
diff --git a/app/views/case_contacts/_case_contact.html.erb b/app/views/case_contacts/_case_contact.html.erb index 7bbf417fdf..9ef9a38b58 100644 --- a/app/views/case_contacts/_case_contact.html.erb +++ b/app/views/case_contacts/_case_contact.html.erb @@ -39,7 +39,7 @@ <% if Pundit.policy(current_user, contact).update? %> <%= render "case_contacts/followup", contact: contact, followup: contact.requested_followup %>
- <%= link_to edit_case_contact_path(contact), class: "text-danger h5" do %> + <%= link_to edit_case_contact_path(contact), class: "text-danger" do %> Edit <% end %>
diff --git a/app/views/case_contacts/_form.html.erb b/app/views/case_contacts/_form.html.erb index ddc95e3148..af0e909ae3 100644 --- a/app/views/case_contacts/_form.html.erb +++ b/app/views/case_contacts/_form.html.erb @@ -114,7 +114,7 @@
- <% if current_organization.show_driving_reimbursement %> + <% if current_organization.show_driving_reimbursement && show_volunteer_reimbursement(casa_cases) %>
<%= form.label :want_driving_reimbursement, "b. Want Driving Reimbursement" %>
diff --git a/app/views/case_contacts/index.html.erb b/app/views/case_contacts/index.html.erb index 4768162333..6a0371b5cf 100644 --- a/app/views/case_contacts/index.html.erb +++ b/app/views/case_contacts/index.html.erb @@ -15,7 +15,9 @@
-<%= form_for_filterrific @filterrific do |f| %> +<%= form_for_filterrific @filterrific, url: case_contacts_path do |f| %> + <%= hidden_field_tag 'casa_case_id', params[:casa_case_id] %> +
Filter by
@@ -129,13 +131,25 @@
<% end %> -<% unless @presenter.case_contacts.any? %> -
-
- <%= params[:filterrific] ? - "No case contacts have been found." : - "You have no case contacts for this case. \ +<% if @presenter.case_contacts.empty? %> + <% if params.key?(:casa_case_id) %> +
+
+

<%= @presenter.display_case_number(params[:casa_case_id].to_i) %>

+ <%= params[:filterrific] ? + "No case contacts have been found." : + "You have no case contacts for this case. \ Please click New Case Contact button above to create a case contact for your youth!" %> +
-
+ <% else %> +
+
+ <%= params[:filterrific] ? + "No case contacts have been found." : + "You have no case contacts for this case. \ + Please click New Case Contact button above to create a case contact for your youth!" %> +
+
+ <% end %> <% end %> diff --git a/app/views/case_court_reports/index.html.erb b/app/views/case_court_reports/index.html.erb index 5efae18f12..ead4376d13 100644 --- a/app/views/case_court_reports/index.html.erb +++ b/app/views/case_court_reports/index.html.erb @@ -2,8 +2,7 @@
-

Report Categories -

+

Report Categories

diff --git a/app/views/case_groups/_form.html.erb b/app/views/case_groups/_form.html.erb new file mode 100644 index 0000000000..a1bbceb160 --- /dev/null +++ b/app/views/case_groups/_form.html.erb @@ -0,0 +1,42 @@ +
+
+
+
+

+ <% if case_group.persisted? %> + Edit Case Group + <% else %> + New Case Group + <% end %> +

+
+
+
+
+
+ <%= form_with model: case_group do |form| %> + <%= render "shared/error_messages", resource: case_group %> + +
+ <%= form.label :name %> + <%= form.text_field :name, required: true %> +
+ +
+ <%= form.label :casa_case_ids, 'Cases' %> + <%= form.collection_select( + :casa_case_ids, + current_organization.casa_cases, + :id, :case_number, + { include_hidden: false }, + { class: "form-control", multiple: true } + ) %> +
+ +
+ <%= button_tag(type: "submit", class: "btn-sm main-btn primary-btn btn-hover") do %> + Submit + <% end %> +
+ <% end %> +
diff --git a/app/views/case_groups/edit.html.erb b/app/views/case_groups/edit.html.erb new file mode 100644 index 0000000000..8bd3a7b8e1 --- /dev/null +++ b/app/views/case_groups/edit.html.erb @@ -0,0 +1 @@ +<%= render 'case_groups/form', case_group: @case_group %> diff --git a/app/views/case_groups/index.html.erb b/app/views/case_groups/index.html.erb new file mode 100644 index 0000000000..80817049ef --- /dev/null +++ b/app/views/case_groups/index.html.erb @@ -0,0 +1,70 @@ +
+
+
+
+

Case Groups

+
+
+
+
+
+
+
+
+

Case Groups

+

+ Case groups are used to bulk create court dates for all cases in a group. For example, if siblings attend the same court data you can create a case group for them and then use the + <%= link_to 'Bulk Court Date', new_bulk_court_date_path %> + form to create a court date for all of them. +

+ <%= link_to new_case_group_path, class: "btn-sm main-btn primary-btn btn-hover" do %> + + New Case Group + <% end %> +
+ + + + + + + + + + + <% @case_groups.each do |case_group| %> + + + + + + + <% end %> + +
NameCase NumbersUpdated AtActions
+ <%= case_group.name %> + +
    + <% case_group.casa_cases.each do |casa_case| %> +
  • + <%= link_to casa_case.case_number, casa_case_path(casa_case) %> +
  • + <% end %> +
+
+ <%= time_ago_in_words(case_group.updated_at) %> ago + + <%= link_to edit_case_group_path(case_group) do %> +
+ +
+ <% end %> + <%= link_to 'Delete', case_group_path(case_group), class: 'btn btn-danger', method: :delete, data: { confirm: "Are you sure that you want to delete this case group?" } %> +
+
+
+
+
+
diff --git a/app/views/case_groups/new.html.erb b/app/views/case_groups/new.html.erb new file mode 100644 index 0000000000..8bd3a7b8e1 --- /dev/null +++ b/app/views/case_groups/new.html.erb @@ -0,0 +1 @@ +<%= render 'case_groups/form', case_group: @case_group %> diff --git a/app/views/checklist_items/_form.html.erb b/app/views/checklist_items/_form.html.erb index b9d9d95237..b3421a746d 100644 --- a/app/views/checklist_items/_form.html.erb +++ b/app/views/checklist_items/_form.html.erb @@ -2,8 +2,7 @@
-

<%= title %> -

+

<%= title %>

diff --git a/app/views/court_dates/_fields.html.erb b/app/views/court_dates/_fields.html.erb new file mode 100644 index 0000000000..d2e51398d4 --- /dev/null +++ b/app/views/court_dates/_fields.html.erb @@ -0,0 +1,42 @@ +
+ <%= form.label :date, "Add Court Date" %> + <%= form.text_field :date, + value: court_date.date&.to_date || Time.zone.now, + data: {provide: "datepicker", date_format: "yyyy/mm/dd"}, + class: "form-control" %> +
+
+ <%= form.label :court_report_due_date, "Add Court Report Due Date" %> + <%= form.text_field :court_report_due_date, + value: court_date.court_report_due_date&.to_date, + data: {provide: "datepicker", date_format: "yyyy/mm/dd"}, + class: "form-control" %> +
+
+ <%= form.label :judge_id, "Judge" %> +
+ <%= form.collection_select( + :judge_id, + Judge.for_organization(current_organization), + :id, :name, + {include_hidden: false, include_blank: "-Select Judge-"}, + {class: "form-control"} + ) %> +
+
+
+ <%= form.label :hearing_type_id, "Hearing type" %> +
+ <%= form.collection_select( + :hearing_type_id, + HearingType.active.for_organization(current_organization), + :id, :name, + {include_hidden: false, include_blank: "-Select Hearing Type-"}, + {class: "form-control"} + ) %> +
+
+
+ <%= render partial: "shared/court_order_list", + locals: {casa_case: casa_case, siblings_casa_cases: nil, form: form, resource: 'court_date'} %> +
diff --git a/app/views/court_dates/_form.html.erb b/app/views/court_dates/_form.html.erb index 13c5bc389c..15ae068b0f 100644 --- a/app/views/court_dates/_form.html.erb +++ b/app/views/court_dates/_form.html.erb @@ -1,5 +1,6 @@
- <%= form_with(model: court_date, url: [casa_case, court_date], local: true) do |form| %> + <%= form_with(model: court_date, url: [casa_case, court_date], local: true, + data: { controller: "extended-nested-form", nested_form_wrapper_selector_value: ".nested-form-wrapper" }) do |form| %> <%= render "/shared/error_messages", resource: court_date %>
@@ -25,48 +26,7 @@
-
- <%= form.label :date, "Add Court Date" %> - <%= form.text_field :date, - value: court_date.date&.to_date || Time.zone.now, - data: {provide: "datepicker", date_format: "yyyy/mm/dd"}, - class: "form-control" %> -
-
- <%= form.label :court_report_due_date, "Add Court Report Due Date" %> - <%= form.text_field :court_report_due_date, - value: court_date.court_report_due_date&.to_date, - data: {provide: "datepicker", date_format: "yyyy/mm/dd"}, - class: "form-control" %> -
-
- <%= form.label :judge_id, "Judge" %> -
- <%= form.collection_select( - :judge_id, - Judge.for_organization(current_organization), - :id, :name, - {include_hidden: false, include_blank: "-Select Judge-"}, - {class: "form-control"} - ) %> -
-
-
- <%= form.label :hearing_type_id, "Hearing type" %> -
- <%= form.collection_select( - :hearing_type_id, - HearingType.active.for_organization(current_organization), - :id, :name, - {include_hidden: false, include_blank: "-Select Hearing Type-"}, - {class: "form-control"} - ) %> -
-
-
- <%= render partial: "shared/court_order_list", - locals: {casa_case: casa_case, siblings_casa_cases: nil, form: form, resource: 'court_date'} %> -
- <% end %> -
+ <%= render 'court_dates/fields', court_date: court_date, form: form, casa_case: casa_case %> +
+ <% end %>
diff --git a/app/views/court_dates/edit.html.erb b/app/views/court_dates/edit.html.erb index f569a2f636..4aebdc61c3 100644 --- a/app/views/court_dates/edit.html.erb +++ b/app/views/court_dates/edit.html.erb @@ -2,7 +2,7 @@
-

Editing Court Date

+

Editing Court Date

diff --git a/app/views/court_dates/new.html.erb b/app/views/court_dates/new.html.erb index 41f4a60648..d4a12652e2 100644 --- a/app/views/court_dates/new.html.erb +++ b/app/views/court_dates/new.html.erb @@ -2,7 +2,7 @@
-

New Court Date

+

New Court Date

diff --git a/app/views/court_dates/show.html.erb b/app/views/court_dates/show.html.erb index f9a4ecfd2e..cec8b045bf 100644 --- a/app/views/court_dates/show.html.erb +++ b/app/views/court_dates/show.html.erb @@ -2,7 +2,7 @@
-

Court Date

+

Court Date

diff --git a/app/views/emancipation_checklists/index.html.erb b/app/views/emancipation_checklists/index.html.erb index ed185fed76..f5dbc7d7f2 100644 --- a/app/views/emancipation_checklists/index.html.erb +++ b/app/views/emancipation_checklists/index.html.erb @@ -2,7 +2,7 @@
-

Emancipation Checklists

+

Emancipation Checklists

diff --git a/app/views/emancipations/show.html.erb b/app/views/emancipations/show.html.erb index f102c440f2..08e38db4fb 100644 --- a/app/views/emancipations/show.html.erb +++ b/app/views/emancipations/show.html.erb @@ -1,6 +1,6 @@
-

Emancipation Checklist

+

Emancipation Checklist

<%= link_to @current_case.case_number, casa_case_path(@current_case) %> <%= link_to casa_case_emancipation_path(@current_case, format: :docx), class: "main-btn primary-btn btn-sm btn-hover" do %> diff --git a/app/views/fund_requests/new.html.erb b/app/views/fund_requests/new.html.erb index 3c5f0229c0..9f6b6cc6a9 100644 --- a/app/views/fund_requests/new.html.erb +++ b/app/views/fund_requests/new.html.erb @@ -2,7 +2,7 @@
-

New Fund Request

+

New Fund Request

diff --git a/app/views/health/index.html.erb b/app/views/health/index.html.erb index c7f7a54cad..37aca71555 100644 --- a/app/views/health/index.html.erb +++ b/app/views/health/index.html.erb @@ -1,4 +1,5 @@
-

Chart: Display App Metric

+

Chart: Display App Metric

+
diff --git a/app/views/hearing_types/_form.html.erb b/app/views/hearing_types/_form.html.erb index 0ece7236ac..a4cd443004 100644 --- a/app/views/hearing_types/_form.html.erb +++ b/app/views/hearing_types/_form.html.erb @@ -2,9 +2,9 @@
-

+

<%= title %> -

+
diff --git a/app/views/layouts/_all_casa_admin_sidebar.html.erb b/app/views/layouts/_all_casa_admin_sidebar.html.erb index 21a44d5e3f..32e29dca71 100644 --- a/app/views/layouts/_all_casa_admin_sidebar.html.erb +++ b/app/views/layouts/_all_casa_admin_sidebar.html.erb @@ -9,14 +9,14 @@
- <%= form.label :learning_type, "Type of Learning" %> + <%= form.label :learning_hour_type_id, "Type of Learning" %>
- <%= form.select :learning_type, - LearningHour.learning_types&.map {|learning| [learning.first.humanize, learning.first]}, - prompt:"Select learning type", - value: @learning_hour.learning_type, - selected: @learning_hour.learning_type %> + <%= form.collection_select :learning_hour_type_id, + LearningHourType.for_organization(current_user.casa_org).active, + :id, + :name, + prompt: "Select learning type", + value: @learning_hour.learning_hour_type_id %>
diff --git a/app/views/learning_hours/_update_form.html.erb b/app/views/learning_hours/_update_form.html.erb index ab9c6f42d0..b24d1ca79e 100644 --- a/app/views/learning_hours/_update_form.html.erb +++ b/app/views/learning_hours/_update_form.html.erb @@ -16,13 +16,14 @@ value: @learning_hour.name %>
- <%= form.label :learning_type, "Type of Learning" %> + <%= form.label :learning_hour_type_id, "Type of Learning" %>
- <%= form.select :learning_type, - LearningHour.learning_types&.map {|learning| [learning.first.humanize, learning.first]}, - prompt:"Select learning type", - value: @learning_hour.learning_type, - selected: @learning_hour.learning_type %> + <%= form.collection_select :learning_hour_type_id, + LearningHourType.for_organization(current_user.casa_org).active, + :id, + :name, + prompt: "Select learning type", + value: @learning_hour.learning_hour_type_id %>
diff --git a/app/views/learning_hours/index.html.erb b/app/views/learning_hours/index.html.erb index 58ccbe3c31..efc2404d35 100644 --- a/app/views/learning_hours/index.html.erb +++ b/app/views/learning_hours/index.html.erb @@ -25,7 +25,7 @@ <%= link_to learning_hour.name, volunteer_learning_hour_path(id: learning_hour.id) %> - <%= (learning_hour.learning_type).humanize %> + <%= learning_hour.learning_hour_type.name %> <%= learning_hour.occurred_at.strftime("%B %d, %Y") %> <% if (learning_hour.duration_hours > 0) %> diff --git a/app/views/learning_hours/show.html.erb b/app/views/learning_hours/show.html.erb index af391447a6..b26b7384d8 100644 --- a/app/views/learning_hours/show.html.erb +++ b/app/views/learning_hours/show.html.erb @@ -20,7 +20,7 @@ <%= @learning_hour.name %> - <%= (@learning_hour.learning_type).humanize %> + <%= @learning_hour.learning_hour_type.name %> <%= @learning_hour.occurred_at.strftime("%B %d, %Y") %> <% if (@learning_hour.duration_hours > 0) %> diff --git a/app/views/mileage_rates/index.html.erb b/app/views/mileage_rates/index.html.erb index f5e30a9fa4..2f4f1ec40d 100644 --- a/app/views/mileage_rates/index.html.erb +++ b/app/views/mileage_rates/index.html.erb @@ -2,7 +2,7 @@
-

Mileage Rates

+

Mileage Rates

diff --git a/app/views/notes/edit.html.erb b/app/views/notes/edit.html.erb index 7e6deff968..50ec0aec29 100644 --- a/app/views/notes/edit.html.erb +++ b/app/views/notes/edit.html.erb @@ -1,5 +1,5 @@ <%= form_with(model: @note, local: true, url: volunteer_note_path(@volunteer, @note)) do |form| %> -

Update Volunteer Note

+

Update Volunteer Note

<%= form.text_area :content, diff --git a/app/views/notifications/_patch_notes.html.erb b/app/views/notifications/_patch_notes.html.erb index 160cee5239..8876caa2b4 100644 --- a/app/views/notifications/_patch_notes.html.erb +++ b/app/views/notifications/_patch_notes.html.erb @@ -1,19 +1,23 @@
+
+ +
-

Patch Notes for <%= deploy_time.strftime("%B %e") %>

+

Patch Notes

+ <%= time_ago_in_words(deploy_time) %> ago
-
- <% patch_notes.each do |type, notes_per_type| %> -
<%= type %>
+ <% patch_notes.each do |type, notes_per_type| %> +
+
<%= type %>
    <% notes_per_type.each do |note_text| %> -
  • <%= note_text %>
  • +
  • <%= simple_format note_text %>
  • <% end %>
- <% end %> -
+
+ <% end %>
diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb index 2bdd7f61db..28bc83229c 100644 --- a/app/views/notifications/index.html.erb +++ b/app/views/notifications/index.html.erb @@ -2,7 +2,7 @@
-

Notifications

+

Notifications

diff --git a/app/views/other_duties/_form.html.erb b/app/views/other_duties/_form.html.erb index 855f59cd31..df0142ba1a 100644 --- a/app/views/other_duties/_form.html.erb +++ b/app/views/other_duties/_form.html.erb @@ -2,8 +2,7 @@
-

<%= title %> -

+

<%= title %>

diff --git a/app/views/other_duties/index.html.erb b/app/views/other_duties/index.html.erb index 4542c5a480..11ad1cd716 100644 --- a/app/views/other_duties/index.html.erb +++ b/app/views/other_duties/index.html.erb @@ -2,8 +2,7 @@
-

Other Duties -

+

Other Duties

diff --git a/app/views/placements/show.html.erb b/app/views/placements/show.html.erb index 447bbb20a3..e8946ea04a 100644 --- a/app/views/placements/show.html.erb +++ b/app/views/placements/show.html.erb @@ -2,7 +2,7 @@
-

Placement

+

Placement

diff --git a/app/views/reimbursements/index.html.erb b/app/views/reimbursements/index.html.erb index 0552117606..0b584a4755 100644 --- a/app/views/reimbursements/index.html.erb +++ b/app/views/reimbursements/index.html.erb @@ -2,7 +2,7 @@
-

Reimbursement Queue

+

Reimbursement Queue

diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb index 24f07beda3..ebc1ecf658 100644 --- a/app/views/reports/index.html.erb +++ b/app/views/reports/index.html.erb @@ -3,7 +3,7 @@
-

Export Data

+

Export Data

diff --git a/app/views/shared/_court_order_form.html.erb b/app/views/shared/_court_order_form.html.erb new file mode 100644 index 0000000000..5154978853 --- /dev/null +++ b/app/views/shared/_court_order_form.html.erb @@ -0,0 +1,19 @@ +
+ +
+ <%= f.text_area :text, cols: 50 %> +
+
+ <%= f.select :implementation_status, + court_order_select_options, + {include_blank: 'Set Implementation Status'}, + {class: 'implementation-status'} %> +
+ + + + <%= f.hidden_field :_destroy %> +
diff --git a/app/views/shared/_court_order_list.erb b/app/views/shared/_court_order_list.erb index d87ab32169..2b09c5a505 100644 --- a/app/views/shared/_court_order_list.erb +++ b/app/views/shared/_court_order_list.erb @@ -1,5 +1,10 @@ <%= form.label :case_court_orders, "Court Orders - Please check that you didn't enter any youth names" %> -
+ +
<% if siblings_casa_cases && siblings_casa_cases.count >= 1 %>
<% siblings_casa_cases_options = siblings_casa_cases.map { |scc| [scc.case_number, scc.id] } %> @@ -13,28 +18,19 @@
<%= button_tag "Copy", type: :button, class: "copy-court-button main-btn primary-btn btn-hover ml-1", id: "copy-court-button" %> - <%= form.hidden_field :casa_case, value: casa_case.id %> + <% if casa_case %> + <%= form.hidden_field :casa_case, value: casa_case.id %> + <% end %>
<% end %> <%= form.fields_for :case_court_orders do |ff| %> -
-
- <%= ff.text_area :text, class: 'input-style-1' %> -
-
- <%= - ff.select :implementation_status, - casa_case.decorate.court_order_select_options, - {include_blank: 'Set Implementation Status'}, - {class: 'implementation-status'} - %> -
- -
+ <%= render "shared/court_order_form", f: ff %> <% end %> + +
-
diff --git a/app/views/shared/_manage_volunteers.html.erb b/app/views/shared/_manage_volunteers.html.erb index 5467f2a454..f19813e165 100644 --- a/app/views/shared/_manage_volunteers.html.erb +++ b/app/views/shared/_manage_volunteers.html.erb @@ -18,6 +18,7 @@ Start Date End Date <% end %> + Enable Reimbursement <% if local_assigns[:button_text] == "Hide unassigned" %> Currently Assigned To <% end %> diff --git a/app/views/static/index.html.erb b/app/views/static/index.html.erb index fa1bd4499e..dd71478cde 100644 --- a/app/views/static/index.html.erb +++ b/app/views/static/index.html.erb @@ -180,6 +180,7 @@
<%= image_tag org.logo, class: "org_logo" %>
+ <%= org.display_name %>
<% end %>
diff --git a/app/views/supervisors/edit.html.erb b/app/views/supervisors/edit.html.erb index 30e597a636..0a90770288 100644 --- a/app/views/supervisors/edit.html.erb +++ b/app/views/supervisors/edit.html.erb @@ -1,6 +1,6 @@
-

Editing Supervisor

+

Editing Supervisor

diff --git a/app/views/supervisors/index.html.erb b/app/views/supervisors/index.html.erb index 84b2927019..a3dc412b42 100644 --- a/app/views/supervisors/index.html.erb +++ b/app/views/supervisors/index.html.erb @@ -2,7 +2,7 @@
-

Supervisors

+

Supervisors

diff --git a/app/views/supervisors/new.html.erb b/app/views/supervisors/new.html.erb index 4d0a79d6ff..dc699c614d 100644 --- a/app/views/supervisors/new.html.erb +++ b/app/views/supervisors/new.html.erb @@ -1,6 +1,6 @@
-

Create New Supervisor

+

Create New Supervisor

diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 2a844e6abb..a68c139751 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -2,7 +2,7 @@
-

Edit Profile

+

Edit Profile

@@ -43,12 +43,12 @@
- <%= form.submit "Update Profile", class: "main-btn primary-btn btn-hover mb-3" %> - - @@ -116,37 +116,41 @@
<%= form_for @user, as: :user, url: users_path do |form| %> -
-

Communication Preferences

-

Tell us how you'd like to receive notifications.

-
- <%= form.check_box :receive_email_notifications, class: "toggle-email-notifications form-check-input" %> - <%= form.label :receive_email_notifications, "Email Me", class: "form-check-label" %> +
+

Communication Preferences

- <% if @user.casa_org.twilio_enabled? %> +
+

Tell us how you'd like to receive notifications.

- <%= form.check_box :receive_sms_notifications, class: "toggle-sms-notifications form-check-input" %> - <%= form.label :receive_sms_notifications, "Text Me", class: "form-check-label" %> + <%= form.check_box :receive_email_notifications, class: "toggle-email-notifications form-check-input" %> + <%= form.label :receive_email_notifications, "Email Me", class: "form-check-label" %>
-
- <%= form.collection_check_boxes("sms_notification_event_ids", SmsNotificationEvent.where(user_type: @user.type), - :id, :name) do |event| %> -
- <%= event.check_box(class: "form-check-input form-check-input", id: "toggle-sms-notification-event") %> - <%= event.label(class: "form-check-label") %> -
- <% end %> -
- <% else %> -
- <%= form.check_box :receive_sms_notifications, class: "toggle-sms-notifications form-check-input", disabled: true %> - <%= form.label :receive_sms_notifications, "Enable Twilio For Text Messaging", class: "form-check-label" %> -
- <% end %> -
- <%= form.submit "Save Preferences", class: "main-btn primary-btn btn-hover mb-3 save-preference" %> + <% if @user.casa_org.twilio_enabled? %> +
+ <%= form.check_box :receive_sms_notifications, class: "toggle-sms-notifications form-check-input" %> + <%= form.label :receive_sms_notifications, "Text Me", class: "form-check-label" %> +
+
+ <%= form.collection_check_boxes("sms_notification_event_ids", SmsNotificationEvent.where(user_type: @user.type), + :id, :name) do |event| %> +
+ <%= event.check_box(class: "form-check-input form-check-input", id: "toggle-sms-notification-event") %> + <%= event.label(class: "form-check-label") %> +
+ <% end %> +
+ <% else %> +
+ <%= form.check_box :receive_sms_notifications, class: "toggle-sms-notifications form-check-input", disabled: true %> + <%= form.label :receive_sms_notifications, "Enable Twilio For Text Messaging", class: "form-check-label" %> +
+ <% end %> + +
+ <%= form.submit "Save Preferences", class: "main-btn primary-btn btn-hover mb-3 save-preference" %> +
<% end %>
diff --git a/app/views/volunteers/index.html.erb b/app/views/volunteers/index.html.erb index 17c93d16f6..852df17456 100644 --- a/app/views/volunteers/index.html.erb +++ b/app/views/volunteers/index.html.erb @@ -2,7 +2,7 @@
-

Volunteers

+

Volunteers

diff --git a/app/views/volunteers/new.html.erb b/app/views/volunteers/new.html.erb index b47f1eb8ee..66aee9f242 100644 --- a/app/views/volunteers/new.html.erb +++ b/app/views/volunteers/new.html.erb @@ -1,6 +1,6 @@
-

Create New Volunteer

+

Create New Volunteer

diff --git a/bin/update b/bin/update index c555b5c723..5468f738b0 100755 --- a/bin/update +++ b/bin/update @@ -18,9 +18,6 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') - puts 'Using correct node version' - system('nvm use') || abort("install correct node version using 'nvm install' and try again") - puts 'Updating yarn' system('yarn install') || abort("install yarn and try again") diff --git a/cc-test-reporter b/cc-test-reporter new file mode 100644 index 0000000000..23a0f531fe Binary files /dev/null and b/cc-test-reporter differ diff --git a/config/application.rb b/config/application.rb index fb94a1763c..2487d061c0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,28 +1,27 @@ require_relative "boot" -require "rails" -# Pick the frameworks you want: -require "active_model/railtie" -require "active_job/railtie" -require "active_record/railtie" -require "active_storage/engine" -require "action_controller/railtie" -require "action_mailer/railtie" -require "action_mailbox/engine" -require "action_text/engine" -require "action_view/railtie" -require "action_cable/engine" -require "sprockets/railtie" -require "rails/test_unit/railtie" +require "rails/all" +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Casa class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 7.0 + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + config.action_mailer.preview_path ||= defined?(Rails.root) ? Rails.root.join("lib", "mailers", "previews") : nil config.eager_load_paths << Rails.root.join("app", "lib", "importers") config.assets.paths << Rails.root.join("app", "assets", "webfonts") - config.load_defaults 7.0 config.active_storage.variant_processor = :mini_magick config.active_storage.content_types_to_serve_as_binary.delete("image/svg+xml") config.serve_static_assets = true diff --git a/config/environments/development.rb b/config/environments/development.rb index 85ee3c0d7c..4a48e22576 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -19,7 +19,7 @@ # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join("tmp", "caching-dev.txt").exist? + if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true @@ -37,7 +37,6 @@ config.active_storage.service = :local # Don't care if the mailer can't send. - config.action_mailer.default_url_options = {host: "localhost", port: 3000} config.action_mailer.delivery_method = :letter_opener config.action_mailer.perform_deliveries = true @@ -59,12 +58,13 @@ # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true + # Suppress logger output for asset requests. + config.assets.quiet = true + config.assets.digest = false + # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true - # Annotate rendered view with file names. - # config.action_view.annotate_rendered_view_with_filenames = true - config.after_initialize do Bullet.enable = true Bullet.console = true @@ -77,7 +77,6 @@ # Uncomment if you wish to allow Action Cable access from any origin. # config.action_cable.disable_request_forgery_protection = true - config.hosts << ".ngrok-free.app" config.assets.digest = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 9c2aa2b9a9..2ffb28407a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -38,20 +38,26 @@ # Apache or NGINX already handles this. config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + # config.assets.compile = false + # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.asset_host = 'http://assets.example.com' + # config.asset_host = "http://assets.example.com" # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :microsoft # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil - # config.action_cable.url = 'wss://example.com/cable' - # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true @@ -88,7 +94,7 @@ # Use a different logger for distributed setups. # require "syslog/logger" - # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new($stdout) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 3621f97f8e..54f47cf15f 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,8 +1,8 @@ # Be sure to restart your server when you modify this file. -# Define an application-wide content security policy -# For further information see the following documentation -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header # Rails.application.configure do # config.content_security_policy do |policy| @@ -20,7 +20,6 @@ # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_directives = %w(script-src) # -# # Report CSP violations to a specified URI. See: -# # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# # Report violations without enforcing the policy. # # config.content_security_policy_report_only = true # end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 13a42c4292..e2fdd60539 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -4,16 +4,16 @@ # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' +# inflect.acronym "RESTful" # end ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym "DSS" + inflect.acronym "DSS" # TODO what is this for? end diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb new file mode 100644 index 0000000000..00f64d71b0 --- /dev/null +++ b/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/config/locales/en.yml b/config/locales/en.yml index c246e57b73..ff36bce61f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -31,6 +31,10 @@ en: hello: "Hello world" + activerecord: + attributes: + learning_hour: + learning_hour_type: "Type of Learning" pundit: default: "Sorry, you are not authorized to perform this action." casa_case_policy: diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 2b353bcca5..78681e090d 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -239,7 +239,7 @@ en: title: Learning Hours form: learning_hours_title: Learning Hours Title - learning_type: Type of Learning + learning_hour_type_id: Type of Learning duration: Learning Duration occurred_on: Occurred On create_button: Create New Learning Hours Entry diff --git a/config/routes.rb b/config/routes.rb index f215b296fb..3da503e3b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,13 @@ root to: "all_casa_admins/sessions#new", as: :unauthenticated_all_casa_root end + resources :preference_sets, only: [] do + collection do + post "/table_state_update/:table_name", to: "preference_sets#table_state_update", as: :table_state_update + get "/table_state/:table_name", to: "preference_sets#table_state", as: :table_state + end + end + resources :health, only: %i[index] do collection do get :case_contacts_creation_times_in_last_week @@ -102,8 +109,12 @@ resources :other_duties, only: %i[new create edit index update] resources :missing_data_reports, only: %i[index] resources :learning_hours_reports, only: %i[index] + resources :learning_hour_types, only: %i[new create edit update] resources :followup_reports, only: :index resources :placement_reports, only: :index + resources :banners, only: %i[index new edit create update destroy] + resources :bulk_court_dates, only: %i[new create] + resources :case_groups, only: %i[index new edit create update destroy] resources :supervisors, except: %i[destroy show], concerns: %i[with_datatable] do member do @@ -136,6 +147,7 @@ get :unassign patch :unassign patch :show_hide_contacts + patch :reimbursement end end resources :case_court_orders, only: %i[destroy] diff --git a/db/migrate/20230627210040_add_allow_reimbursement_to_case_assignments.rb b/db/migrate/20230627210040_add_allow_reimbursement_to_case_assignments.rb new file mode 100644 index 0000000000..b6b3167aee --- /dev/null +++ b/db/migrate/20230627210040_add_allow_reimbursement_to_case_assignments.rb @@ -0,0 +1,5 @@ +class AddAllowReimbursementToCaseAssignments < ActiveRecord::Migration[7.0] + def change + add_column :case_assignments, :allow_reimbursement, :boolean, default: true + end +end diff --git a/db/migrate/20230712080040_add_foreign_key_creator_id_to_note.rb b/db/migrate/20230712080040_add_foreign_key_creator_id_to_note.rb new file mode 100644 index 0000000000..8194801ee0 --- /dev/null +++ b/db/migrate/20230712080040_add_foreign_key_creator_id_to_note.rb @@ -0,0 +1,9 @@ +class AddForeignKeyCreatorIdToNote < ActiveRecord::Migration[7.0] + def up + add_foreign_key :notes, :users, column: :creator_id, validate: false + end + + def down + remove_foreign_key :notes, :creator_id + end +end diff --git a/db/migrate/20230728135743_create_action_text_tables.action_text.rb b/db/migrate/20230728135743_create_action_text_tables.action_text.rb new file mode 100644 index 0000000000..c186553435 --- /dev/null +++ b/db/migrate/20230728135743_create_action_text_tables.action_text.rb @@ -0,0 +1,27 @@ +# This migration comes from action_text (originally 20180528164100) +class CreateActionTextTables < ActiveRecord::Migration[6.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :action_text_rich_texts, id: primary_key_type do |t| + t.string :name, null: false + t.text :body, size: :long + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + + t.timestamps + + t.index [:record_type, :record_id, :name], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end + + private + + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/migrate/20230728140249_create_banners.rb b/db/migrate/20230728140249_create_banners.rb new file mode 100644 index 0000000000..ecf7cf515b --- /dev/null +++ b/db/migrate/20230728140249_create_banners.rb @@ -0,0 +1,12 @@ +class CreateBanners < ActiveRecord::Migration[7.0] + def change + create_table :banners do |t| + t.references :casa_org, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + t.string :name + t.boolean :active, default: false + + t.timestamps + end + end +end diff --git a/db/migrate/20230729143126_create_learning_hour_types.rb b/db/migrate/20230729143126_create_learning_hour_types.rb new file mode 100644 index 0000000000..f569677913 --- /dev/null +++ b/db/migrate/20230729143126_create_learning_hour_types.rb @@ -0,0 +1,12 @@ +class CreateLearningHourTypes < ActiveRecord::Migration[7.0] + def change + create_table :learning_hour_types do |t| + t.references :casa_org, null: false, foreign_key: true + t.string :name + t.boolean :active, default: true + t.integer :position, default: 1 + + t.timestamps + end + end +end diff --git a/db/migrate/20230729145310_add_reference_learning_hour_types.rb b/db/migrate/20230729145310_add_reference_learning_hour_types.rb new file mode 100644 index 0000000000..0805cebb83 --- /dev/null +++ b/db/migrate/20230729145310_add_reference_learning_hour_types.rb @@ -0,0 +1,7 @@ +class AddReferenceLearningHourTypes < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + + def change + add_reference :learning_hours, :learning_hour_type, validate: false, index: {algorithm: :concurrently} + end +end diff --git a/db/migrate/20230729145351_add_foreign_key_learning_hour_types.rb b/db/migrate/20230729145351_add_foreign_key_learning_hour_types.rb new file mode 100644 index 0000000000..7944f3d531 --- /dev/null +++ b/db/migrate/20230729145351_add_foreign_key_learning_hour_types.rb @@ -0,0 +1,5 @@ +class AddForeignKeyLearningHourTypes < ActiveRecord::Migration[7.0] + def change + add_foreign_key :learning_hours, :learning_hour_types, validate: false + end +end diff --git a/db/migrate/20230729145419_validate_foreign_key_learning_hour_types.rb b/db/migrate/20230729145419_validate_foreign_key_learning_hour_types.rb new file mode 100644 index 0000000000..531bad9f1a --- /dev/null +++ b/db/migrate/20230729145419_validate_foreign_key_learning_hour_types.rb @@ -0,0 +1,5 @@ +class ValidateForeignKeyLearningHourTypes < ActiveRecord::Migration[7.0] + def change + validate_foreign_key :learning_hours, :learning_hour_types + end +end diff --git a/db/migrate/20230729154529_create_case_groups.rb b/db/migrate/20230729154529_create_case_groups.rb new file mode 100644 index 0000000000..3e4225eafa --- /dev/null +++ b/db/migrate/20230729154529_create_case_groups.rb @@ -0,0 +1,10 @@ +class CreateCaseGroups < ActiveRecord::Migration[7.0] + def change + create_table :case_groups do |t| + t.references :casa_org, null: false, foreign_key: true + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20230729154545_create_case_group_memberships.rb b/db/migrate/20230729154545_create_case_group_memberships.rb new file mode 100644 index 0000000000..8db73518f3 --- /dev/null +++ b/db/migrate/20230729154545_create_case_group_memberships.rb @@ -0,0 +1,10 @@ +class CreateCaseGroupMemberships < ActiveRecord::Migration[7.0] + def change + create_table :case_group_memberships do |t| + t.references :case_group, null: false, foreign_key: true + t.references :casa_case, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230729213608_remove_hearing_type_id_and_judge_id_from_casa_cases.rb b/db/migrate/20230729213608_remove_hearing_type_id_and_judge_id_from_casa_cases.rb new file mode 100644 index 0000000000..6fe4998003 --- /dev/null +++ b/db/migrate/20230729213608_remove_hearing_type_id_and_judge_id_from_casa_cases.rb @@ -0,0 +1,6 @@ +class RemoveHearingTypeIdAndJudgeIdFromCasaCases < ActiveRecord::Migration[7.0] + def change + safety_assured { remove_column :casa_cases, :hearing_type_id } + safety_assured { remove_column :casa_cases, :judge_id } + end +end diff --git a/db/migrate/20230730103110_remove_learning_type.rb b/db/migrate/20230730103110_remove_learning_type.rb new file mode 100644 index 0000000000..aab9117027 --- /dev/null +++ b/db/migrate/20230730103110_remove_learning_type.rb @@ -0,0 +1,5 @@ +class RemoveLearningType < ActiveRecord::Migration[7.0] + def change + safety_assured { remove_column :learning_hours, :learning_type, :integer, default: 5, not_null: true } + end +end diff --git a/db/schema.rb b/db/schema.rb index e0285fa682..cc561343e1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_07_10_025852) do +ActiveRecord::Schema[7.0].define(version: 2023_07_30_103110) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "action_text_rich_texts", force: :cascade do |t| + t.string "name", null: false + t.text "body" + t.string "record_type", null: false + t.bigint "record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true + end + create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -78,6 +88,17 @@ t.index ["reset_password_token"], name: "index_all_casa_admins_on_reset_password_token", unique: true end + create_table "banners", force: :cascade do |t| + t.bigint "casa_org_id", null: false + t.bigint "user_id", null: false + t.string "name" + t.boolean "active", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["casa_org_id"], name: "index_banners_on_casa_org_id" + t.index ["user_id"], name: "index_banners_on_user_id" + end + create_table "casa_case_contact_types", force: :cascade do |t| t.bigint "contact_type_id", null: false t.bigint "casa_case_id", null: false @@ -104,17 +125,13 @@ t.bigint "casa_org_id", null: false t.datetime "birth_month_year_youth", precision: nil t.datetime "court_report_due_date", precision: nil - t.bigint "hearing_type_id" t.boolean "active", default: true, null: false - t.bigint "judge_id" t.datetime "court_report_submitted_at", precision: nil t.integer "court_report_status", default: 0 t.string "slug" t.datetime "date_in_care" t.index ["casa_org_id"], name: "index_casa_cases_on_casa_org_id" t.index ["case_number", "casa_org_id"], name: "index_casa_cases_on_case_number_and_casa_org_id", unique: true - t.index ["hearing_type_id"], name: "index_casa_cases_on_hearing_type_id" - t.index ["judge_id"], name: "index_casa_cases_on_judge_id" t.index ["slug"], name: "index_casa_cases_on_slug" end @@ -152,6 +169,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "hide_old_contacts", default: false + t.boolean "allow_reimbursement", default: true t.index ["casa_case_id"], name: "index_case_assignments_on_casa_case_id" t.index ["volunteer_id"], name: "index_case_assignments_on_volunteer_id" end @@ -196,6 +214,23 @@ t.index ["court_date_id"], name: "index_case_court_orders_on_court_date_id" end + create_table "case_group_memberships", force: :cascade do |t| + t.bigint "case_group_id", null: false + t.bigint "casa_case_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["casa_case_id"], name: "index_case_group_memberships_on_casa_case_id" + t.index ["case_group_id"], name: "index_case_group_memberships_on_case_group_id" + end + + create_table "case_groups", force: :cascade do |t| + t.bigint "casa_org_id", null: false + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["casa_org_id"], name: "index_case_groups_on_casa_org_id" + end + create_table "checklist_items", force: :cascade do |t| t.integer "hearing_type_id" t.text "description", null: false @@ -348,15 +383,26 @@ t.index ["user_id"], name: "index_languages_users_on_user_id" end + create_table "learning_hour_types", force: :cascade do |t| + t.bigint "casa_org_id", null: false + t.string "name" + t.boolean "active", default: true + t.integer "position", default: 1 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["casa_org_id"], name: "index_learning_hour_types_on_casa_org_id" + end + create_table "learning_hours", force: :cascade do |t| t.bigint "user_id", null: false - t.integer "learning_type", default: 5 t.string "name", null: false t.integer "duration_minutes", null: false t.integer "duration_hours", null: false t.datetime "occurred_at", precision: nil, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "learning_hour_type_id" + t.index ["learning_hour_type_id"], name: "index_learning_hours_on_learning_hour_type_id" t.index ["user_id"], name: "index_learning_hours_on_user_id" end @@ -566,6 +612,8 @@ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "additional_expenses", "case_contacts" add_foreign_key "addresses", "users" + add_foreign_key "banners", "casa_orgs" + add_foreign_key "banners", "users" add_foreign_key "casa_case_emancipation_categories", "casa_cases" add_foreign_key "casa_case_emancipation_categories", "emancipation_categories" add_foreign_key "casa_cases", "casa_orgs" @@ -576,14 +624,20 @@ add_foreign_key "case_contacts", "casa_cases" add_foreign_key "case_contacts", "users", column: "creator_id" add_foreign_key "case_court_orders", "casa_cases" + add_foreign_key "case_group_memberships", "casa_cases" + add_foreign_key "case_group_memberships", "case_groups" + add_foreign_key "case_groups", "casa_orgs" add_foreign_key "court_dates", "casa_cases" add_foreign_key "emancipation_options", "emancipation_categories" add_foreign_key "followups", "users", column: "creator_id" add_foreign_key "judges", "casa_orgs" add_foreign_key "languages", "casa_orgs" + add_foreign_key "learning_hour_types", "casa_orgs" + add_foreign_key "learning_hours", "learning_hour_types" add_foreign_key "learning_hours", "users" add_foreign_key "mileage_rates", "casa_orgs" add_foreign_key "mileage_rates", "users" + add_foreign_key "notes", "users", column: "creator_id" add_foreign_key "other_duties", "users", column: "creator_id" add_foreign_key "patch_notes", "patch_note_groups" add_foreign_key "patch_notes", "patch_note_types" diff --git a/db/seeds.rb b/db/seeds.rb index c10e3dc587..1026c42493 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -62,6 +62,7 @@ def active_record_classes HearingType, Judge, Language, + LearningHourType, MileageRate, Supervisor, SupervisorVolunteer, diff --git a/db/seeds/db_populator.rb b/db/seeds/db_populator.rb index 12bffd0cad..2dac6e18ea 100644 --- a/db/seeds/db_populator.rb +++ b/db/seeds/db_populator.rb @@ -50,6 +50,7 @@ def create_org(options_hash) create_judges(casa_org) create_languages(casa_org) create_mileage_rates(casa_org) + create_learning_hour_types(casa_org) casa_org end @@ -198,7 +199,8 @@ def create_cases(casa_org, options) case_number: case_number, court_report_submitted_at: court_report_submitted ? Date.today : nil, court_report_status: court_report_submitted ? :submitted : :not_submitted, - birth_month_year_youth: birth_month_year_youth + birth_month_year_youth: birth_month_year_youth, + date_in_care: Date.today - (rand * 1500) ) new_court_date = CourtDate.find_or_create_by!( casa_case: new_casa_case, @@ -365,6 +367,16 @@ def create_mileage_rates(casa_org) end end + def create_learning_hour_types(casa_org) + learning_types = %w[book movie webinar conference other] + + learning_types.each do |learning_type| + learning_hour_type = casa_org.learning_hour_types.new(name: learning_type.capitalize) + learning_hour_type.position = 99 if learning_type == "other" + learning_hour_type.save + end + end + def most_recent_past_court_date(casa_case_id) CourtDate.where( "date < ? AND casa_case_id = ?", diff --git a/doc/LINUX_SETUP.md b/doc/LINUX_SETUP.md index 9377a3882f..5fc213d7fb 100644 --- a/doc/LINUX_SETUP.md +++ b/doc/LINUX_SETUP.md @@ -40,6 +40,9 @@ sudo apt update # Install Postgres 12 sudo apt install -y postgresql-12 +# Turn the server on +systemctl start postgresql + # Add user to Postgres: sudo -u postgres psql -c "CREATE USER $USER WITH CREATEDB" diff --git a/lib/tasks/deployment/20230720000759_update_howard_court_report_fix.rake b/lib/tasks/deployment/20230720000759_update_howard_court_report_fix.rake new file mode 100644 index 0000000000..4682cf1683 --- /dev/null +++ b/lib/tasks/deployment/20230720000759_update_howard_court_report_fix.rake @@ -0,0 +1,18 @@ +namespace :after_party do + desc "Deployment task: update_howard_court_report_fix" + task update_howard_court_report_fix: :environment do + puts "Running deploy task 'update_howard_court_report_fix'" + + casa_org = CasaOrg.find_by(name: "Howard County CASA") + if casa_org + casa_org.court_report_template.attach(io: File.new(Rails.root.join("app", "documents", "templates", "howard_county_report_template.docx")), filename: "howard_county_report_template.docx") + else + Bugsnag.notify("No Howard County CASA found for rake task update_howard_court_report_template") + end + + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end diff --git a/package.json b/package.json index 22354b3f75..d33aa7645d 100644 --- a/package.json +++ b/package.json @@ -12,21 +12,24 @@ "build:css:dev": "sass app/assets/stylesheets/application.scss app/assets/builds/application.css --load-path=node_modules --watch" }, "dependencies": { + "@babel/core": "^7.0.0", "@fortawesome/fontawesome-free": "^6.4.0", + "@hotwired/stimulus": "^3.2.1", "@popperjs/core": "^2.11.8", "@rails/actioncable": "^7.0.6", + "@rails/actiontext": "^7.0.6", "@rails/activestorage": "^7.0.6", "@rails/ujs": "^7.0.6", "add2calendar": "^1.1.8", "bindings": "^1.5.0", - "bootstrap": "5.3.0", + "bootstrap": "5.3.1", "bootstrap-datepicker": "^1.10.0", "bootstrap-scss": "^5.3.0", "bootstrap-select": "^1.13.18", - "chart.js": "^4.3.0", + "chart.js": "^4.3.2", "chartjs-adapter-luxon": "^1.3.1", - "datatables.net-dt": "^1.13.4", - "esbuild": "^0.18.11", + "datatables.net-dt": "^1.13.6", + "esbuild": "^0.18.17", "faker": "^5.5.3", "jquery": "^3.6.4", "js-cookie": "^3.0.5", @@ -34,18 +37,20 @@ "lodash": "^4.17.21", "luxon": "^3.3.0", "popper.js": "^1.16.1", - "sass": "^1.63.6", + "sass": "^1.64.2", "select2": "^4.0.13", "select2-bootstrap-5-theme": "^1.3.0", + "stimulus-rails-nested-form": "^4.1.0", "strftime": "^0.10.2", "sweetalert2": "^11.3.5", + "trix": "^2.0.5", "turbolinks": "^5.2.0" }, "version": "0.1.0", "devDependencies": { - "@babel/preset-env": "^7.22.5", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", + "@babel/preset-env": "^7.22.9", + "jest": "^29.6.2", + "jest-environment-jsdom": "^29.6.2", "markdown-toc": "^1.2.0", "standard": "^17.1.0", "start-server-and-test": "^2.0.0" diff --git a/spec/components/notification_component_spec.rb b/spec/components/notification_component_spec.rb index 382864fc39..704493872b 100644 --- a/spec/components/notification_component_spec.rb +++ b/spec/components/notification_component_spec.rb @@ -6,6 +6,8 @@ let(:followup_with_note) { create(:notification, :followup_with_note) } let(:followup_no_note) { create(:notification, :followup_without_note) } let(:followup_read) { create(:notification, :followup_read) } + let(:emancipation_checklist_reminder) { create(:notification, :emancipation_checklist_reminder) } + let(:youth_birthday) { create(:notification, :youth_birthday) } it "renders a followup with note" do component = described_class.new(notification: followup_with_note.to_notification) @@ -40,4 +42,20 @@ expect(page).to have_css("a.bg-light.text-muted") expect(page).not_to have_css("i.fas.fa-bell") end + + it "renders an emancipation checklist reminder" do + component = described_class.new(notification: emancipation_checklist_reminder.to_notification) + + render_inline(component) + expect(page).to have_text("Emancipation Checklist Reminder") + expect(page).to have_text(emancipation_checklist_reminder.to_notification.message) + end + + it "renders a youth birthday notification" do + component = described_class.new(notification: youth_birthday.to_notification) + + render_inline(component) + expect(page).to have_text("Youth Birthday") + expect(page).to have_text(youth_birthday.to_notification.message) + end end diff --git a/spec/controllers/case_court_reports_controller_spec.rb b/spec/controllers/case_court_reports_controller_spec.rb index a3d9a60258..a839b7e764 100644 --- a/spec/controllers/case_court_reports_controller_spec.rb +++ b/spec/controllers/case_court_reports_controller_spec.rb @@ -1,6 +1,7 @@ require "rails_helper" RSpec.describe CaseCourtReportsController, type: :controller do + include DownloadHelpers describe "GET index" do context "when volunteer" do it "successfully accesses 'Generate Court Report' page" do @@ -155,9 +156,9 @@ get :show, params: {id: case_number, format: "docx"} - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_header_contains?("YOUR CASA ORG’S NUMBER")).to eq(true) + expect(header_text(docx_response)).to include("YOUR CASA ORG’S NUMBER") end end context "when a custom template is set" do @@ -180,9 +181,9 @@ get :show, params: {id: case_number, format: "docx"} - document_inspector = DocxInspector.new(docx_contents: response.body) + download_docx = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?("Did you forget to enter your court orders?")).to eq(true) + expect(download_docx.paragraphs.map(&:to_s)).to include("Did you forget to enter your court orders?") end end end @@ -227,9 +228,8 @@ get :show, params: {id: case_number, format: "docx"} - document_inspector = DocxInspector.new(docx_contents: response.body) - - expect(document_inspector.word_list_header_contains?("YOUR CASA ORG’S NUMBER")).to eq(true) + docx = Docx::Document.open(StringIO.new(response.body)) + expect(header_text(docx)).to include("YOUR CASA ORG’S NUMBER") end end context "when a custom template is set" do @@ -252,9 +252,9 @@ get :show, params: {id: case_number, format: "docx"} - document_inspector = DocxInspector.new(docx_contents: response.body) + download_docx = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?("Did you forget to enter your court orders?")).to eq(true) + expect(download_docx.paragraphs.map(&:to_s)).to include("Did you forget to enter your court orders?") end end end diff --git a/spec/decorators/preference_set_decorator_spec.rb b/spec/decorators/preference_set_decorator_spec.rb new file mode 100644 index 0000000000..b4f885674d --- /dev/null +++ b/spec/decorators/preference_set_decorator_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +# Specs in this file have access to a helper object that includes +# the PreferenceSetsHelper. For example: +# +# describe PreferenceSetsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe PreferenceSetsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/factories/banners.rb b/spec/factories/banners.rb new file mode 100644 index 0000000000..1356f7ec01 --- /dev/null +++ b/spec/factories/banners.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :banner do + casa_org + association :user, factory: :supervisor + name { "Volunteer Survey" } + active { true } + content { "Please fill out this survey" } + end +end diff --git a/spec/factories/casa_orgs.rb b/spec/factories/casa_orgs.rb index 9fd356ec36..bfdcb99331 100644 --- a/spec/factories/casa_orgs.rb +++ b/spec/factories/casa_orgs.rb @@ -12,6 +12,5 @@ trait :with_logo do logo { Rack::Test::UploadedFile.new(Rails.root.join("spec", "fixtures", "org_logo.jpeg")) } end - twilio_enabled { true } end end diff --git a/spec/factories/case_assignments.rb b/spec/factories/case_assignments.rb index 030a6e3a83..03d4b12ed1 100644 --- a/spec/factories/case_assignments.rb +++ b/spec/factories/case_assignments.rb @@ -6,6 +6,7 @@ end active { true } + allow_reimbursement { true } casa_case do if pre_transition @@ -19,6 +20,10 @@ create(:volunteer, casa_org: @overrides[:casa_case].try(:casa_org) || casa_org) end + trait :disallow_reimbursement do + allow_reimbursement { false } + end + trait :inactive do active { false } end diff --git a/spec/factories/case_court_report_context.rb b/spec/factories/case_court_report_context.rb index da700dc112..f3402cf5e1 100644 --- a/spec/factories/case_court_report_context.rb +++ b/spec/factories/case_court_report_context.rb @@ -4,7 +4,9 @@ transient do casa_case { nil } + court_date { nil } volunteer { nil } + case_court_orders { nil } path_to_report { Rails.root.join("tmp", "test_report.docx").to_s } path_to_template { Rails.root.join("app", "documents", "templates", "default_report_template.docx").to_s } end @@ -21,7 +23,9 @@ case_id: casa_case_for_context.id, volunteer_id: volunteer_for_context.id, path_to_report: path_to_report, - path_to_template: path_to_template + path_to_template: path_to_template, + court_date: court_date, + case_court_orders: case_court_orders ) } end diff --git a/spec/factories/case_group_memberships.rb b/spec/factories/case_group_memberships.rb new file mode 100644 index 0000000000..b3689867ac --- /dev/null +++ b/spec/factories/case_group_memberships.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :case_group_membership do + case_group { create(:case_group) } + casa_case { create(:casa_case) } + end +end diff --git a/spec/factories/case_groups.rb b/spec/factories/case_groups.rb new file mode 100644 index 0000000000..7ce94f9892 --- /dev/null +++ b/spec/factories/case_groups.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :case_group do + casa_org { CasaOrg.first || create(:casa_org) } + name { "A family" } + + after(:build) do |case_group, _| + if case_group.case_group_memberships.empty? + case_group.case_group_memberships.build(casa_case: create(:casa_case)) + end + end + end +end diff --git a/spec/factories/learning_hour_types.rb b/spec/factories/learning_hour_types.rb new file mode 100644 index 0000000000..bc61eaf102 --- /dev/null +++ b/spec/factories/learning_hour_types.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :learning_hour_type do + casa_org { CasaOrg.first || create(:casa_org) } + sequence(:name) { |n| "Learning Hour Type #{n}" } + active { true } + position { 1 } + end +end diff --git a/spec/factories/learning_hours.rb b/spec/factories/learning_hours.rb index 598366f747..45545eea0f 100644 --- a/spec/factories/learning_hours.rb +++ b/spec/factories/learning_hours.rb @@ -5,5 +5,6 @@ duration_minutes { 25 } duration_hours { 1 } occurred_at { 2.days.ago } + learning_hour_type { LearningHourType.first || create(:learning_hour_type) } end end diff --git a/spec/factories/notifications.rb b/spec/factories/notifications.rb index 20e8718c0d..5aee066e16 100644 --- a/spec/factories/notifications.rb +++ b/spec/factories/notifications.rb @@ -46,5 +46,25 @@ } initialize_with { new(params: params) } end + + trait :emancipation_checklist_reminder do + type { "EmancipationChecklistReminderNotification" } + params { + { + casa_case: create(:casa_case) + } + } + initialize_with { new(params: params) } + end + + trait :youth_birthday do + type { "YouthBirthdayNotification" } + params { + { + casa_case: create(:casa_case) + } + } + initialize_with { new(params: params) } + end end end diff --git a/spec/factories/preference_sets.rb b/spec/factories/preference_sets.rb index cd8d8c5345..4a9e4193cf 100644 --- a/spec/factories/preference_sets.rb +++ b/spec/factories/preference_sets.rb @@ -2,5 +2,6 @@ factory :preference_set do user case_volunteer_columns { {} } + table_state { {} } end end diff --git a/spec/factories/volunteers.rb b/spec/factories/volunteers.rb index 2f7ca7be3a..c53bced149 100644 --- a/spec/factories/volunteers.rb +++ b/spec/factories/volunteers.rb @@ -45,5 +45,11 @@ create(:supervisor_volunteer, :inactive, volunteer: user, supervisor: evaluator.supervisor) end end + + trait :with_disasllow_reimbursement do + after(:create) do |user, _| + create(:case_assignment, :disallow_reimbursement, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user) + end + end end end diff --git a/app/documents/templates/default_past_court_date_template.docx b/spec/fixtures/files/default_past_court_date_template.docx similarity index 54% rename from app/documents/templates/default_past_court_date_template.docx rename to spec/fixtures/files/default_past_court_date_template.docx index 36bbd233a9..6354bd3ffe 100644 Binary files a/app/documents/templates/default_past_court_date_template.docx and b/spec/fixtures/files/default_past_court_date_template.docx differ diff --git a/spec/helpers/case_contacts_helper_spec.rb b/spec/helpers/case_contacts_helper_spec.rb index 043991a1e6..ad7f5935cc 100644 --- a/spec/helpers/case_contacts_helper_spec.rb +++ b/spec/helpers/case_contacts_helper_spec.rb @@ -84,4 +84,30 @@ expect(helper.set_contact_made_false(case_contact)).to eq(false) end end + + describe "#show_volunteer_reimbursement" do + before(:each) do + @casa_cases = [] + @casa_cases << create(:casa_case) + @casa_org = @casa_cases[0].casa_org + @current_user = create(:volunteer, casa_org: @casa_org) + end + + it "returns true if allow_reimbursement is true" do + create(:case_assignment, casa_case: @casa_cases[0], volunteer: @current_user) + allow(helper).to receive(:current_user).and_return(@current_user) + expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(true) + end + + it "returns false if allow_reimbursement is false" do + create(:case_assignment, :disallow_reimbursement, casa_case: @casa_cases[0], volunteer: @current_user) + allow(helper).to receive(:current_user).and_return(@current_user) + expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(false) + end + + it "returns false if no case_assigmnents are found" do + allow(helper).to receive(:current_user).and_return(@current_user) + expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(false) + end + end end diff --git a/spec/helpers/court_orders_helper_spec.rb b/spec/helpers/court_orders_helper_spec.rb new file mode 100644 index 0000000000..bdf346e09c --- /dev/null +++ b/spec/helpers/court_orders_helper_spec.rb @@ -0,0 +1,11 @@ +require "rails_helper" + +RSpec.describe CourtDatesHelper do + describe "#court_order_select_options" do + context "when no court orders" do + it "empty" do + expect(helper.court_order_select_options).to eq([["Unimplemented", "unimplemented"], ["Partially implemented", "partially_implemented"], ["Implemented", "implemented"]]) + end + end + end +end diff --git a/spec/helpers/preference_sets_helper_spec.rb b/spec/helpers/preference_sets_helper_spec.rb new file mode 100644 index 0000000000..b4f885674d --- /dev/null +++ b/spec/helpers/preference_sets_helper_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +# Specs in this file have access to a helper object that includes +# the PreferenceSetsHelper. For example: +# +# describe PreferenceSetsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe PreferenceSetsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/sidebar_helper_spec.rb b/spec/helpers/sidebar_helper_spec.rb index 1596803cd5..962fb4791a 100644 --- a/spec/helpers/sidebar_helper_spec.rb +++ b/spec/helpers/sidebar_helper_spec.rb @@ -31,8 +31,7 @@ context "when accessing an index route" do it "renders sidebar menu item as an active link" do - allow(helper).to receive(:action_name).and_return("index") - allow(helper).to receive(:current_page?).with({controller: "supervisors", action: "index"}).and_return(true) + helper.request.path = "/supervisors" menu_item = helper.menu_item(label: "Supervisors", path: supervisors_path, visible: true) @@ -42,14 +41,31 @@ context "when accessing an all casa admin menu item" do it "renders the sidebar menu item as an active link" do - allow(helper).to receive(:action_name).and_return("index") - allow(helper).to receive(:current_page?).with({controller: "patch_notes", action: "index"}).and_return(true) + helper.request.path = "/all_casa_admins/patch_notes" menu_item = helper.menu_item(label: "Patch Notes", path: all_casa_admins_patch_notes_path, visible: true) expect(menu_item).to match "class=\"list-group-item active\"" end end + + context "when accessing an volunteer emancipation checklist" do + it "renders the sidebar menu item as an active link with no redirect" do + helper.request.path = "/emancipation_checklists" + + menu_item = helper.menu_item(label: "Emancipation Checklist(s)", path: emancipation_checklists_path, visible: true) + + expect(menu_item).to match "class=\"list-group-item active\"" + end + + it "renders the sidebar menu item as an active link with redirect" do + helper.request.path = "/casa_cases/some-case-slug/emancipation" + + menu_item = helper.menu_item(label: "Emancipation Checklist(s)", path: emancipation_checklists_path, visible: true) + + expect(menu_item).to match "class=\"list-group-item active\"" + end + end end end diff --git a/spec/lib/tasks/case_contact_types_reminder_spec.rb b/spec/lib/tasks/case_contact_types_reminder_spec.rb index c3f2a293f3..751878097f 100644 --- a/spec/lib/tasks/case_contact_types_reminder_spec.rb +++ b/spec/lib/tasks/case_contact_types_reminder_spec.rb @@ -6,6 +6,7 @@ let!(:casa_org) do create( :casa_org, + twilio_enabled: true, twilio_phone_number: "+15555555555", twilio_account_sid: "articuno34", twilio_api_key_sid: "Aladdin", diff --git a/spec/lib/tasks/no_contact_made_reminder_spec.rb b/spec/lib/tasks/no_contact_made_reminder_spec.rb index a3b72d5595..99f08689a1 100644 --- a/spec/lib/tasks/no_contact_made_reminder_spec.rb +++ b/spec/lib/tasks/no_contact_made_reminder_spec.rb @@ -6,6 +6,7 @@ let!(:casa_org) do create( :casa_org, + twilio_enabled: true, twilio_phone_number: "+15555555555", twilio_account_sid: "articuno34", twilio_api_key_sid: "Aladdin", diff --git a/spec/models/banner_spec.rb b/spec/models/banner_spec.rb new file mode 100644 index 0000000000..27e0b6df3e --- /dev/null +++ b/spec/models/banner_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +RSpec.describe Banner, type: :model do + describe "#valid?" do + it "does not allow multiple active banners for same organization" do + casa_org = create(:casa_org) + supervisor = create(:supervisor) + create(:banner, casa_org: casa_org, user: supervisor) + + banner = build(:banner, casa_org: casa_org, user: supervisor) + expect(banner).to_not be_valid + end + + it "does allow multiple active banners for different organization" do + casa_org = create(:casa_org) + supervisor = create(:supervisor, casa_org: casa_org) + create(:banner, casa_org: casa_org, user: supervisor) + + another_org = create(:casa_org) + another_supervisor = create(:supervisor, casa_org: another_org) + banner = build(:banner, casa_org: another_org, user: another_supervisor) + expect(banner).to be_valid + end + end +end diff --git a/spec/models/case_contact_spec.rb b/spec/models/case_contact_spec.rb index effdbc000c..6b9c457a5e 100644 --- a/spec/models/case_contact_spec.rb +++ b/spec/models/case_contact_spec.rb @@ -450,12 +450,9 @@ groups_with_types = case_contact.contact_groups_with_types - expect(groups_with_types).to eql( - { - "Family" => ["Parent"], - "Health" => ["Medical Professional", "Other Therapist"] - } - ) + expect(groups_with_types.keys).to match_array(["Family", "Health"]) + expect(groups_with_types["Family"]).to match_array(["Parent"]) + expect(groups_with_types["Health"]).to match_array(["Medical Professional", "Other Therapist"]) end end diff --git a/spec/models/case_court_report_context_spec.rb b/spec/models/case_court_report_context_spec.rb index 93736d66e4..e57dd2cdf7 100644 --- a/spec/models/case_court_report_context_spec.rb +++ b/spec/models/case_court_report_context_spec.rb @@ -45,6 +45,19 @@ end end + context "when they specify a specific court date they are interested in looking at" do + it "contains the selected court date in a human readable format" do + court_date_1 = create(:court_date, date: 2.months.since) + court_date_2 = create(:court_date, date: 5.months.since) + + casa_case.court_dates << court_date_1 + casa_case.court_dates << court_date_2 + + court_report_context = build(:case_court_report_context, casa_case: casa_case, court_date: court_date_2) + expect(court_report_context.context[:casa_case][:court_date]).to eq("June 1, 2021") + end + end + context "when there are no future court dates" do let(:past_court_date) { create(:court_date, date: 2.months.ago) } @@ -247,6 +260,14 @@ casa_case.case_court_orders << court_order_unimplemented end + context "when using specified orders to a specific casa date" do + it "does not lean on orders of the casa case if specified directly" do + court_order = build(:case_court_order, text: "Some Court Text", implementation_status: :implemented) + court_report_context = build(:case_court_report_context, casa_case: casa_case, case_court_orders: [court_order]).context + expect(court_report_context[:case_court_orders]).to eq([{order: "Some Court Text", status: "Implemented"}]) + end + end + it "has a list of court orders the same length as all the court orders in the case" do expect(court_report_context[:case_court_orders].length).to eq(casa_case.case_court_orders.length) end diff --git a/spec/models/case_court_report_spec.rb b/spec/models/case_court_report_spec.rb index 41e2a18492..1c747c4f19 100644 --- a/spec/models/case_court_report_spec.rb +++ b/spec/models/case_court_report_spec.rb @@ -4,6 +4,7 @@ require "sablon" RSpec.describe CaseCourtReport, type: :model do + include DownloadHelpers let(:path_to_template) { Rails.root.join("app", "documents", "templates", "default_report_template.docx").to_s } let(:path_to_report) { Rails.root.join("tmp", "test_report.docx").to_s } @@ -107,7 +108,6 @@ let(:contact_type) { create(:contact_type, name: document_data[:case_contact_type]) } let(:case_contact) { create(:case_contact, contact_made: false, occurred_at: document_data[:case_contact_time]) } let(:court_order) { create(:case_court_order, implementation_status: :partially_implemented) } - let(:document_inspector) { DocxInspector.new(docx_contents: report.generate_to_string) } before(:each) do casa_case_with_contacts.casa_org.update_attribute(:address, document_data[:org_address]) @@ -124,47 +124,79 @@ end it "displays the org address" do - expect(document_inspector.word_list_header_contains?(document_data[:org_address])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + expect(header_text(docx_response)).to include(document_data[:org_address]) end it "displays today's date formatted" do - expect(document_inspector.word_list_document_contains?(Date.current.strftime("%B %-d, %Y"))).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{Date.current.strftime("%B %-d, %Y")}.*/) end it "displays the case hearing date date formatted" do - expect(document_inspector.word_list_document_contains?(document_data[:case_hearing_date].strftime("%B %-d, %Y"))).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_hearing_date].strftime("%B %-d, %Y")}.*/) end it "displays the case number" do - expect(document_inspector.word_list_document_contains?(document_data[:case_number])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_number]}.*/) end - it "displays th case contact type" do - expect(document_inspector.word_list_document_contains?(document_data[:case_contact_type])).to eq(true) + it "displays the case contact type" do + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:case_contact_type]}.*/) end - it "displays the case contact tiime date formatted" do - expect(document_inspector.word_list_document_contains?("#{document_data[:case_contact_time].strftime("%-m/%d")}*")).to eq(true) + it "displays the case contact time date formatted" do + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:case_contact_time].strftime("%-m/%d")}.*/) end it "displays the text" do - expect(document_inspector.word_list_document_contains?(document_data[:text])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:text]}.*/) end it "displays the order status" do - expect(document_inspector.word_list_document_contains?("Partially implemented")).to eq(true) # Order Status + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include("Partially implemented") end it "displays the volunteer name" do - expect(document_inspector.word_list_document_contains?(document_data[:volunteer_name])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:volunteer_name]}.*/) end it "displays the volunteer case assignment date formatted" do - expect(document_inspector.word_list_document_contains?(document_data[:volunteer_case_assignment_date].strftime("%B %-d, %Y"))).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:volunteer_case_assignment_date].strftime("%B %-d, %Y")}.*/) end it "displayes the supervisor name" do - expect(document_inspector.word_list_document_contains?(document_data[:supervisor_name])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(/#{document_data[:supervisor_name]}.*/) end end @@ -199,7 +231,6 @@ let(:contact_type) { create(:contact_type, name: document_data[:case_contact_type]) } let(:case_contact) { create(:case_contact, contact_made: false, occurred_at: document_data[:case_contact_time]) } let(:court_order) { create(:case_court_order, implementation_status: :partially_implemented) } - let(:document_inspector) { DocxInspector.new(docx_contents: report.generate_to_string) } before(:each) do casa_case.casa_org.update_attribute(:address, document_data[:org_address]) @@ -213,31 +244,52 @@ end it "displays today's date formatted" do - expect(document_inspector.word_list_document_contains?(Date.current.strftime("%B %-d, %Y"))).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{Date.current.strftime("%B %-d, %Y")}.*/) end it "displays the case hearing date formatted" do - expect(document_inspector.word_list_document_contains?(document_data[:case_hearing_date].strftime("%B %-d, %Y"))).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_hearing_date].strftime("%B %-d, %Y")}.*/) end - it "displays the case numbet" do - expect(document_inspector.word_list_document_contains?(document_data[:case_number])).to eq(true) + it "displays the case number" do + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + expect(docx_response.paragraphs.map(&:to_s)).to include(/.*#{document_data[:case_number]}.*/) end it "displays the case contact type" do - expect(document_inspector.word_list_document_contains?(document_data[:case_contact_type])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(document_data[:case_contact_type]) end it "displays the case contact time formatted" do - expect(document_inspector.word_list_document_contains?("#{document_data[:case_contact_time].strftime("%-m/%d")}*")).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include(document_data[:case_contact_time].strftime("%-m/%d*")) end it "displays the test" do - expect(document_inspector.word_list_document_contains?(document_data[:text])).to eq(true) + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include("This text shall not be strikingly similar to other text in the document") end it "displays the order status" do - expect(document_inspector.word_list_document_contains?("Partially implemented")).to eq(true) # Order Status + docx_response = Docx::Document.open(StringIO.new(report.generate_to_string)) + + table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + + expect(table_data).to include("Partially implemented") end end end @@ -262,10 +314,10 @@ describe "when court orders has different implementation statuses" do let(:casa_case) { create(:casa_case, case_number: "Sample-Case-12345") } - let(:court_order_implemented) { create(:case_court_order, casa_case: casa_case, text: "K6N-ce8|NuXnht(", implementation_status: :implemented) } - let(:court_order_unimplemented) { create(:case_court_order, casa_case: casa_case, text: "'q\"tE1LP-9W>,2)", implementation_status: :unimplemented) } - let(:court_order_partially_implemented) { create(:case_court_order, casa_case: casa_case, text: "ZmCw@w@\d`&roct", implementation_status: :partially_implemented) } - let(:court_order_not_specified) { create(:case_court_order, casa_case: casa_case, text: "(4WqOL7e'FRYd@%", implementation_status: nil) } + let(:court_order_implemented) { create(:case_court_order, casa_case: casa_case, text: "an order that got done", implementation_status: :implemented) } + let(:court_order_unimplemented) { create(:case_court_order, casa_case: casa_case, text: "an order that got not done", implementation_status: :unimplemented) } + let(:court_order_partially_implemented) { create(:case_court_order, casa_case: casa_case, text: "an order that got kinda done", implementation_status: :partially_implemented) } + let(:court_order_not_specified) { create(:case_court_order, casa_case: casa_case, text: "what is going on", implementation_status: nil) } let(:args) do { case_id: casa_case.id, @@ -275,7 +327,6 @@ end let(:context) { CaseCourtReportContext.new(args).context } let(:case_report) { CaseCourtReport.new(path_to_template: path_to_template, context: context) } - let(:document_inspector) { DocxInspector.new(docx_contents: case_report.generate_to_string) } before(:each) do casa_case.case_court_orders << court_order_implemented @@ -285,39 +336,57 @@ end it "contains the case number" do - expect(document_inspector.word_list_document_contains?(casa_case.case_number)).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{casa_case.case_number}*/) end it "contains the court order text" do - expect(document_inspector.word_list_document_contains?(court_order_implemented.text)).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/#{court_order_implemented.text}.*/) end it "contains the exact value of 'Implemented'" do - expect(document_inspector.word_list_document_contains?("Implemented")).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/Implemented.*/) end it "contains the court order text" do - expect(document_inspector.word_list_document_contains?(court_order_unimplemented.text)).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/#{court_order_unimplemented.text}.*/) end it "contains the exact value of 'Unimplemented'" do - expect(document_inspector.word_list_document_contains?("Unimplemented")).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/Unimplemented.*/) end it "contains the court order text" do - expect(document_inspector.word_list_document_contains?(court_order_partially_implemented.text)).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/#{court_order_partially_implemented.text}.*/) end it "contains the exact value of 'Partially implemented'" do - expect(document_inspector.word_list_document_contains?("Partially implemented")).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/Partially implemented.*/) end it "contains the court order text" do - expect(document_inspector.word_list_document_contains?(court_order_not_specified.text)).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/#{court_order_not_specified.text}.*/) end it "contains the exact value of 'Not specified'" do - expect(document_inspector.word_list_document_contains?("Not specified")).to eq(true) + docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string)) + + expect(table_text(docx_response)).to include(/Not specified.*/) end end end diff --git a/spec/models/case_group_membership_spec.rb b/spec/models/case_group_membership_spec.rb new file mode 100644 index 0000000000..530aa2100d --- /dev/null +++ b/spec/models/case_group_membership_spec.rb @@ -0,0 +1,9 @@ +require "rails_helper" + +RSpec.describe CaseGroupMembership, type: :model do + it "has a valid factory" do + case_group_membership = build(:case_group_membership) + + expect(case_group_membership).to be_valid + end +end diff --git a/spec/models/case_group_spec.rb b/spec/models/case_group_spec.rb new file mode 100644 index 0000000000..9c9f2ff637 --- /dev/null +++ b/spec/models/case_group_spec.rb @@ -0,0 +1,9 @@ +require "rails_helper" + +RSpec.describe CaseGroup, type: :model do + it "has a valid factory" do + case_group = build(:case_group) + + expect(case_group).to be_valid + end +end diff --git a/spec/models/court_date_spec.rb b/spec/models/court_date_spec.rb index 0013601c4c..7acb1c5d69 100644 --- a/spec/models/court_date_spec.rb +++ b/spec/models/court_date_spec.rb @@ -117,13 +117,6 @@ end end - describe "#generate_report" do - subject { court_date.generate_report } - - # TODO write a better test for this - it { is_expected.not_to be_nil } - end - describe "#display_name" do subject { court_date.display_name } it "contains case number and date" do diff --git a/spec/models/judge_spec.rb b/spec/models/judge_spec.rb index 0fad9053b9..21a63aa56b 100644 --- a/spec/models/judge_spec.rb +++ b/spec/models/judge_spec.rb @@ -5,29 +5,31 @@ it { is_expected.to validate_presence_of(:name) } it "has a valid factory" do - expect(build(:judge).valid?).to be true - end + judge = build(:judge) - describe "for_organization" do - let!(:casa_org_1) { create(:casa_org) } - let!(:casa_org_2) { create(:casa_org) } - let!(:record_1) { create(:judge, casa_org: casa_org_1) } - let!(:record_2) { create(:judge, casa_org: casa_org_2) } + expect(judge).to be_valid + end + describe ".for_organization" do it "returns only records matching the specified organization" do + casa_org_1 = create(:casa_org) + casa_org_2 = create(:casa_org) + record_1 = create(:judge, casa_org: casa_org_1) + record_2 = create(:judge, casa_org: casa_org_2) + expect(described_class.for_organization(casa_org_1)).to eq([record_1]) expect(described_class.for_organization(casa_org_2)).to eq([record_2]) end end describe "default scope" do - let(:casa_org) { create(:casa_org) } - let(:judges) do - 5.times.map { create(:judge, casa_org: casa_org) } - end - it "orders alphabetically by name" do - expect(described_class.for_organization(casa_org)).to eq(judges.sort_by(&:name)) + casa_org = create(:casa_org) + judge1 = create(:judge, name: "Gamma") + judge2 = create(:judge, name: "Alpha") + judge3 = create(:judge, name: "Epsilon") + + expect(described_class.for_organization(casa_org)).to eq [judge2, judge3, judge1] end end end diff --git a/spec/models/learning_hour_spec.rb b/spec/models/learning_hour_spec.rb index 6d4a967488..cf6d370248 100644 --- a/spec/models/learning_hour_spec.rb +++ b/spec/models/learning_hour_spec.rb @@ -7,10 +7,10 @@ expect(learning_hour.errors[:name]).to eq(["/ Title cannot be blank"]) end - it "has a learning_type" do - learning_hour = build_stubbed(:learning_hour, learning_type: nil) + it "has a learning_hour_type" do + learning_hour = build_stubbed(:learning_hour, learning_hour_type: nil) expect(learning_hour).to_not be_valid - expect(learning_hour.errors[:learning_type]).to eq(["can't be blank"]) + expect(learning_hour.errors[:learning_hour_type]).to eq(["must exist"]) end context "duration_hours is zero" do diff --git a/spec/models/learning_hour_type_spec.rb b/spec/models/learning_hour_type_spec.rb new file mode 100644 index 0000000000..6673a8321c --- /dev/null +++ b/spec/models/learning_hour_type_spec.rb @@ -0,0 +1,54 @@ +require "rails_helper" + +RSpec.describe LearningHourType, type: :model do + it { is_expected.to belong_to(:casa_org) } + it { is_expected.to validate_presence_of(:name) } + + it "has a valid factory" do + expect(build(:learning_hour_type).valid?).to be true + end + + it "has unique names for the specified organization" do + casa_org_1 = create(:casa_org) + casa_org_2 = create(:casa_org) + create(:learning_hour_type, casa_org: casa_org_1, name: "Book") + expect { + create(:learning_hour_type, casa_org: casa_org_1, name: "Book") + }.to raise_error(ActiveRecord::RecordInvalid) + expect { + create(:learning_hour_type, casa_org: casa_org_1, name: "Book ") + }.to raise_error(ActiveRecord::RecordInvalid) + expect { + create(:learning_hour_type, casa_org: casa_org_1, name: "book") + }.to raise_error(ActiveRecord::RecordInvalid) + expect { + create(:learning_hour_type, casa_org: casa_org_2, name: "Book") + }.to_not raise_error + end + + describe "for_organization" do + let!(:casa_org_1) { create(:casa_org) } + let!(:casa_org_2) { create(:casa_org) } + let!(:record_1) { create(:learning_hour_type, casa_org: casa_org_1) } + let!(:record_2) { create(:learning_hour_type, casa_org: casa_org_2) } + + it "returns only records matching the specified organization" do + expect(described_class.for_organization(casa_org_1)).to eq([record_1]) + expect(described_class.for_organization(casa_org_2)).to eq([record_2]) + end + end + + describe "default scope" do + let(:casa_org) { create(:casa_org) } + + it "orders alphabetically by position and then name" do + create(:learning_hour_type, casa_org: casa_org, name: "Book") + create(:learning_hour_type, casa_org: casa_org, name: "Webinar") + create(:learning_hour_type, casa_org: casa_org, name: "Other", position: 99) + create(:learning_hour_type, casa_org: casa_org, name: "YouTube") + + type_names = %w[Book Webinar YouTube Other] + expect(described_class.for_organization(casa_org).map(&:name)).to eq(type_names) + end + end +end diff --git a/spec/models/learning_hours_report_spec.rb b/spec/models/learning_hours_report_spec.rb index 94340e1ff9..3229ad42d6 100644 --- a/spec/models/learning_hours_report_spec.rb +++ b/spec/models/learning_hours_report_spec.rb @@ -2,40 +2,38 @@ require "csv" RSpec.describe LearningHoursReport, type: :model do - let!(:casa_org) { build(:casa_org) } - let!(:users) { create_list(:user, 3, casa_org: casa_org) } - describe "#to_csv" do - let(:result) { CSV.parse(described_class.new(casa_org.id).to_csv) } - context "when there are learning hours" do - let!(:learning_hours) do - [ - create(:learning_hour, user: users[0]), - create(:learning_hour, user: users[1], learning_type: :movie), - create(:learning_hour, user: users[2], learning_type: :webinar) - ] - end - it "includes all learning hours" do + casa_org = build(:casa_org) + users = create_list(:user, 3, casa_org: casa_org) + learning_hour_types = create_list(:learning_hour_type, 3) + learning_hours = + [ + create(:learning_hour, user: users[0], learning_hour_type: learning_hour_types[0]), + create(:learning_hour, user: users[1], learning_hour_type: learning_hour_types[1]), + create(:learning_hour, user: users[2], learning_hour_type: learning_hour_types[2]) + ] + result = CSV.parse(described_class.new(casa_org.id).to_csv) + expect(result.length).to eq(learning_hours.length + 1) - learning_hours.each_with_index do |learning_hour, index| - wait_for_csv_parse(learning_hour, result[index + 1], %i[name learning_type]) - expect(result[index + 1]).to eq([ - learning_hour.user.display_name, - learning_hour.name, - learning_hour.learning_type, - "#{learning_hour.duration_hours}:#{learning_hour.duration_minutes}", - learning_hour.occurred_at.strftime("%F") - ]) + + result.each_with_index do |row, index| + next if index.zero? + expect(row[0]).to eq learning_hours[index - 1].user.display_name + expect(row[1]).to eq learning_hours[index - 1].name + expect(row[2]).to eq learning_hours[index - 1].learning_hour_type.name + expect(row[3]).to eq "#{learning_hours[index - 1].duration_hours}:#{learning_hours[index - 1].duration_minutes}" + expect(row[4]).to eq learning_hours[index - 1].occurred_at.strftime("%F") end end end context "when there are no learning hours" do - let(:result) { CSV.parse(described_class.new(casa_org.id).to_csv) } - it "returns only the header" do + casa_org = build(:casa_org) + result = CSV.parse(described_class.new(casa_org.id).to_csv) + expect(result.length).to eq(1) expect(result[0]).to eq([ "Volunteer Name", diff --git a/spec/models/volunteer_spec.rb b/spec/models/volunteer_spec.rb index 06453aba78..837afde88a 100644 --- a/spec/models/volunteer_spec.rb +++ b/spec/models/volunteer_spec.rb @@ -341,11 +341,12 @@ describe "#learning_hours_spent_in_one_year" do let(:volunteer) { create :volunteer } - let!(:leraning_hours) do + let(:learning_hour_type) { create :learning_hour_type } + let!(:learning_hours) do [ - create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30), - create(:learning_hour, user: volunteer, duration_hours: 3, duration_minutes: 45), - create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30, occurred_at: 2.year.ago) + create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30, learning_hour_type: learning_hour_type), + create(:learning_hour, user: volunteer, duration_hours: 3, duration_minutes: 45, learning_hour_type: learning_hour_type), + create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30, occurred_at: 2.year.ago, learning_hour_type: learning_hour_type) ] end diff --git a/spec/policies/supervisor_policy_spec.rb b/spec/policies/supervisor_policy_spec.rb index e2a234917f..10580a6a47 100644 --- a/spec/policies/supervisor_policy_spec.rb +++ b/spec/policies/supervisor_policy_spec.rb @@ -74,7 +74,7 @@ end end context "different org" do - let(:record) { build_stubbed(:supervisor, casa_org: build_stubbed(:casa_org)) } + let(:record) { build_stubbed(:supervisor, casa_org: different_organization) } context "when user is admin" do it "cannot edit a supervisor" do is_expected.not_to permit(casa_admin, record) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 7cc3751208..07a625aecb 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,9 +5,9 @@ require "rspec/rails" # Add additional requires below this line. Rails is not loaded until this point! require "pundit/rspec" -require "webdrivers" unless ENV["DOCKER"] require "view_component/test_helpers" require "capybara/rspec" +require "action_text/system_test_helper" # Require all support folder files Dir[Rails.root.join("spec", "support", "**", "*.rb")].sort.each { |f| require f } @@ -31,12 +31,12 @@ config.include PunditHelper, type: :view config.include SessionHelper, type: :view config.include SessionHelper, type: :request - config.include CsvExporterHelper, type: :model config.include TemplateHelper config.include Warden::Test::Helpers config.include ViewComponent::TestHelpers, type: :component config.include ViewComponent::SystemTestHelpers, type: :component config.include Capybara::RSpecMatchers, type: :component + config.include ActionText::SystemTestHelper, type: :system config.after do Warden.test_reset! diff --git a/spec/requests/casa_admins_spec.rb b/spec/requests/casa_admins_spec.rb index 480976f9a4..5a4b26bbd7 100644 --- a/spec/requests/casa_admins_spec.rb +++ b/spec/requests/casa_admins_spec.rb @@ -359,7 +359,10 @@ subject { post casa_admins_path, params: {casa_admin: params} } before do - sign_in_as_admin + org = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: org) + + sign_in admin @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("admin") @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("admin") @short_io_stub = WebMockHelper.short_io_stub_sms @@ -417,7 +420,7 @@ end it "does not send SMS when Twilio has an error" do - org = create(:casa_org, twilio_account_sid: "articuno31") + org = create(:casa_org, twilio_account_sid: "articuno31", twilio_enabled: true) admin = build(:casa_admin, casa_org: org) sign_in admin params[:phone_number] = "+12222222222" diff --git a/spec/requests/case_court_reports_spec.rb b/spec/requests/case_court_reports_spec.rb index 9d8207ebc1..42ca28a2b0 100644 --- a/spec/requests/case_court_reports_spec.rb +++ b/spec/requests/case_court_reports_spec.rb @@ -1,6 +1,7 @@ require "rails_helper" RSpec.describe "/case_court_reports", type: :request do + include DownloadHelpers let(:volunteer) { create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor) } before do @@ -162,9 +163,9 @@ request_generate_court_report get JSON.parse(response.body)["link"] - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_header_contains?("YOUR CASA ORG’S NUMBER")).to eq(true) + expect(header_text(docx_response)).to include("YOUR CASA ORG’S NUMBER") end context "as a supervisor" do @@ -203,9 +204,9 @@ it "uses the custom template" do get JSON.parse(response.body)["link"] - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?("Did you forget to enter your court orders?")).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include("Did you forget to enter your court orders?") end end end @@ -233,9 +234,9 @@ get JSON.parse(response.body)["link"] - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(I18n.l(user_different_timezone.at(server_time).to_date, format: :full, default: nil))).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include("Date Written: #{I18n.l(user_different_timezone.at(server_time).to_date, format: :full, default: nil)}") end end end diff --git a/spec/requests/court_dates_spec.rb b/spec/requests/court_dates_spec.rb index befe8a62d4..a78a7748d2 100644 --- a/spec/requests/court_dates_spec.rb +++ b/spec/requests/court_dates_spec.rb @@ -1,6 +1,7 @@ require "rails_helper" RSpec.describe "/casa_cases/:casa_case_id/court_dates/:id", type: :request do + include DownloadHelpers let(:admin) { create(:casa_admin) } let(:casa_case) { court_date.casa_case } let(:court_date) { create(:court_date) } @@ -37,7 +38,12 @@ describe "GET /show" do subject(:show) { get casa_case_court_date_path(casa_case, court_date) } - before { show } + before do + casa_org = court_date.casa_case.casa_org + casa_org.court_report_template.attach(io: File.new(Rails.root.join("spec", "fixtures", "files", "default_past_court_date_template.docx")), filename: "test_past_date_template.docx") + casa_org.court_report_template.save! + show + end context "when the request is authenticated" do it { expect(response).to have_http_status(:success) } @@ -61,9 +67,9 @@ it "displays the court date" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(court_date.date.to_s)).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include(/December 25, 2020/) end context "when a judge is attached" do @@ -73,9 +79,9 @@ it "includes the judge's name in the document" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(judge.name)).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{judge.name}/) end end @@ -86,11 +92,11 @@ it "includes None for the judge's name in the document" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(judge.name)).to eq(false) - expect(document_inspector.word_list_document_contains?("Judge:")).to eq(true) # Judge: None - expect(document_inspector.word_list_document_contains?("None")).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).not_to include(/#{judge.name}/) + expect(docx_response.paragraphs.map(&:to_s)).to include(/Judge:/) + expect(docx_response.paragraphs.map(&:to_s)).to include(/None/) end end @@ -101,9 +107,9 @@ it "includes the hearing type in the document" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(hearing_type.name)).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include(/#{hearing_type.name}/) end end @@ -114,11 +120,11 @@ it "includes None for the hearing type in the document" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?(hearing_type.name)).to eq(false) - expect(document_inspector.word_list_document_contains?("Hearing Type:")).to eq(true) # Hearing Type: None - expect(document_inspector.word_list_document_contains?("None")).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).not_to include(/#{hearing_type.name}/) + expect(docx_response.paragraphs.map(&:to_s)).to include(/Hearing Type:/) + expect(docx_response.paragraphs.map(&:to_s)).to include(/None/) end end @@ -129,11 +135,11 @@ it "includes court order info" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?("Court Orders:")).to eq(true) # Court Orders: - expect(document_inspector.word_list_document_contains?(court_date.case_court_orders.first.text)).to eq(true) - expect(document_inspector.word_list_document_contains?(court_date.case_court_orders.first.implementation_status.humanize)).to eq(true) + expect(docx_response.paragraphs.map(&:to_s)).to include(/Court Orders/) + expect(table_text(docx_response)).to include(/#{court_date.case_court_orders.first.text}/) + expect(table_text(docx_response)).to include(/#{court_date.case_court_orders.first.implementation_status.humanize}/) end end @@ -144,9 +150,9 @@ it "does not include court orders section" do show - document_inspector = DocxInspector.new(docx_contents: response.body) + docx_response = Docx::Document.open(StringIO.new(response.body)) - expect(document_inspector.word_list_document_contains?("Court Orders:")).to eq(false) # Court Orders: + expect(docx_response.paragraphs.map(&:to_s)).not_to include(/Court Orders/) end end end diff --git a/spec/requests/languages_spec.rb b/spec/requests/languages_spec.rb index 024b9cf414..be4ea04ee9 100644 --- a/spec/requests/languages_spec.rb +++ b/spec/requests/languages_spec.rb @@ -1,47 +1,43 @@ require "rails_helper" RSpec.describe LanguagesController, type: :request do - let(:organization) { create(:casa_org) } - let!(:admin) { create(:casa_admin, casa_org: organization) } - let!(:volunteer) { create(:volunteer, casa_org: organization) } - let!(:random_lang) { create(:language, casa_org: organization) } - let!(:casa_case) { create(:casa_case, casa_org: organization) } + describe "POST /create" do + context "when request params are valid" do + it "should create a new language" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) - context "when logged in as an admin user" do - before do - sign_in admin - allow(controller).to receive(:current_organization).and_return(organization) - end - - describe "#create" do - context "when request params are valid" do - it "should create a new language" do - post languages_path, params: { - language: { - name: "Spanish" - } + sign_in admin + post languages_path, params: { + language: { + name: "Spanish" } + } - expect(response.status).to eq 302 - expect(response).to redirect_to(edit_casa_org_path(organization.id)) - expect(flash[:notice]).to eq "Language was successfully created." - end + expect(response.status).to eq 302 + expect(response).to redirect_to(edit_casa_org_path(organization.id)) + expect(flash[:notice]).to eq "Language was successfully created." end end + end + + describe "PATCH /update" do + context "when request params are valid" do + it "should create a new language" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + language = create(:language, casa_org: organization) - describe "#update" do - context "when request params are valid" do - it "should create a new language" do - patch language_path(random_lang), params: { - language: { - name: "Spanishes" - } + sign_in admin + patch language_path(language), params: { + language: { + name: "Spanishes" } + } - expect(response.status).to eq 302 - expect(response).to redirect_to(edit_casa_org_path(organization.id)) - expect(flash[:notice]).to eq "Language was successfully updated." - end + expect(response.status).to eq 302 + expect(response).to redirect_to(edit_casa_org_path(organization.id)) + expect(flash[:notice]).to eq "Language was successfully updated." end end end diff --git a/spec/requests/notifications_spec.rb b/spec/requests/notifications_spec.rb index b2448dd165..ff1d0431db 100644 --- a/spec/requests/notifications_spec.rb +++ b/spec/requests/notifications_spec.rb @@ -41,8 +41,8 @@ let(:patch_note_group_no_volunteers) { create(:patch_note_group, :only_supervisors_and_admins) } let(:patch_note_type_a) { create(:patch_note_type, name: "patch_note_type_a") } let(:patch_note_type_b) { create(:patch_note_type, name: "patch_note_type_b") } - let(:patch_note_1) { create(:patch_note, note: "*Sy@\\(\\\"Q7", patch_note_type: patch_note_type_a) } - let(:patch_note_2) { create(:patch_note, note: "(W!;Ros>cIWNKX}", patch_note_type: patch_note_type_b) } + let(:patch_note_1) { create(:patch_note, note: "Patch Note 1", patch_note_type: patch_note_type_a) } + let(:patch_note_2) { create(:patch_note, note: "Patch Note B", patch_note_type: patch_note_type_b) } before do patch_note_1.update(created_at: Date.new(2020, 12, 31), patch_note_group: patch_note_group_all_users) diff --git a/spec/requests/preference_sets_spec.rb b/spec/requests/preference_sets_spec.rb new file mode 100644 index 0000000000..20f9124ec6 --- /dev/null +++ b/spec/requests/preference_sets_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" + +RSpec.describe "PreferenceSets", type: :request do + let!(:supervisor) { create(:supervisor) } + let!(:preference_set) { supervisor.preference_set } + let!(:table_state) { {"columns" => [{"visible" => "false"}, {"visible" => "true"}, {"visible" => "false"}, {"visible" => "true"}]} } + + describe "GET /preference_sets/table_state/volunteers_table" do + subject { get "/preference_sets/table_state/volunteers_table" } + + before do + sign_in supervisor + supervisor.preference_set.table_state["volunteers_table"] = table_state + supervisor.preference_set.save! + end + + it "returns the table state" do + subject + expect(response.body).to eq(table_state.to_json) + end + end + + describe "POST /preference_sets/table_state_update/volunteers_table" do + subject { post "/preference_sets/table_state_update/volunteers_table", params: {table_name: "volunteers_table", table_state: table_state} } + before do + sign_in supervisor + end + it "updates the table state" do + subject + preference_set.reload + expect(preference_set.table_state["volunteers_table"]).to eq(table_state) + end + + it "returns a 200 OK status" do + subject + expect(response).to have_http_status(:ok) + end + end +end diff --git a/spec/controllers/reimbursements_controller_spec.rb b/spec/requests/reimbursements_spec.rb similarity index 52% rename from spec/controllers/reimbursements_controller_spec.rb rename to spec/requests/reimbursements_spec.rb index ab1b6d4a71..db993f4c3f 100644 --- a/spec/controllers/reimbursements_controller_spec.rb +++ b/spec/requests/reimbursements_spec.rb @@ -1,18 +1,13 @@ require "rails_helper" -RSpec.describe ReimbursementsController, type: :controller do +RSpec.describe ReimbursementsController, type: :request do let(:admin) { create(:casa_admin) } let(:casa_org) { admin.casa_org } + let(:case_contact) { create(:case_contact) } - before do - allow(controller).to receive(:authenticate_user!).and_return(:admin) - end - - describe "as admin" do - before do - allow(controller).to receive(:current_user).and_return(admin) - end + before { sign_in(admin) } + describe "GET /index" do it "calls ReimbursementPolicy::Scope to filter reimbursements" do contact_relation = double(CaseContact) allow(contact_relation).to receive_message_chain( @@ -21,7 +16,7 @@ :filter_by_reimbursement_status ).and_return([]) allow(ReimbursementPolicy::Scope).to receive(:new) - .with(controller.current_user, CaseContact.joins(:casa_case)) + .with(admin, CaseContact.joins(:casa_case)) .and_return(double(resolve: contact_relation)) expect(contact_relation).to receive_message_chain( @@ -30,24 +25,30 @@ :filter_by_reimbursement_status ) - get :index + get reimbursements_url expect(ReimbursementPolicy::Scope).to have_received(:new) - .with(controller.current_user, CaseContact.joins(:casa_case)) + .with(admin, CaseContact.joins(:casa_case)) end + end - xit "can change reimbursement status to complete" do - patch :mark_as_complete + describe "PATCH /mark_as_complete" do + it "changes reimbursement status to complete" do + patch reimbursement_mark_as_complete_url(case_contact, case_contact: {reimbursement_complete: true}) expect(response).to redirect_to(reimbursements_path) expect(response).to have_http_status(:redirect) - expect(assigns(:reimbursements)).to eq([]) + expect(case_contact.reload.reimbursement_complete).to be_truthy end + end + + describe "PATCH /mark_as_needs_review" do + before { case_contact.update(reimbursement_complete: true) } - xit "can change reimbursement status to needs review" do - patch :mark_as_needs_review + it "changes reimbursement status to needs review" do + patch reimbursement_mark_as_needs_review_url(case_contact, case_contact: {reimbursement_complete: false}) expect(response).to redirect_to(reimbursements_path) expect(response).to have_http_status(:redirect) - expect(assigns(:reimbursements)).to eq([]) + expect(case_contact.reload.reimbursement_complete).to be_falsey end end end diff --git a/spec/requests/supervisors_spec.rb b/spec/requests/supervisors_spec.rb index c87df9a3a5..dba272c1d2 100644 --- a/spec/requests/supervisors_spec.rb +++ b/spec/requests/supervisors_spec.rb @@ -117,6 +117,7 @@ context "different org" do let(:diff_org) { create(:casa_org) } let(:supervisor_diff_org) { create(:supervisor, casa_org: diff_org) } + it "admin cannot view the edit supervisor page" do sign_in_as_admin @@ -125,6 +126,7 @@ expect(response).to redirect_to root_path expect(response.request.flash[:notice]).to eq "Sorry, you are not authorized to perform this action." end + it "supervisor cannot view the edit supervisor page" do sign_in_as_supervisor @@ -239,12 +241,6 @@ end describe "POST /create" do - before do - @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("supervisor") - @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("supervisor") - @short_io_stub = WebMockHelper.short_io_stub_sms - end - let(:params) do { supervisor: { @@ -255,6 +251,9 @@ end it "sends an invitation email" do + org = create(:casa_org, twilio_enabled: true) + admin = build(:casa_admin, casa_org: org) + sign_in admin post supervisors_url, params: params @@ -265,9 +264,16 @@ end it "sends a SMS when a phone number exists" do - sign_in admin + org = create(:casa_org, twilio_enabled: true) + admin = build(:casa_admin, casa_org: org) + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("supervisor") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("supervisor") + @short_io_stub = WebMockHelper.short_io_stub_sms params[:supervisor][:phone_number] = "+12222222222" + + sign_in admin post supervisors_url, params: params + expect(@short_io_stub).to have_been_requested.times(2) expect(@twilio_activation_success_stub).to have_been_requested.times(1) expect(response).to have_http_status(:redirect) @@ -276,8 +282,15 @@ end it "does not send a SMS if phone number not given" do + org = create(:casa_org, twilio_enabled: true) + admin = build(:casa_admin, casa_org: org) + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("supervisor") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("supervisor") + @short_io_stub = WebMockHelper.short_io_stub_sms + sign_in admin post supervisors_url, params: params + expect(@short_io_stub).to have_been_requested.times(0) expect(@twilio_activation_success_stub).to have_been_requested.times(0) expect(@twilio_activation_error_stub).to have_been_requested.times(0) @@ -288,8 +301,11 @@ it "does not send a SMS if Twilio has an error" do # ex. credentials entered wrong - org = create(:casa_org, twilio_account_sid: "articuno31") - admin = build(:casa_admin, casa_org: org) + org = create(:casa_org, twilio_account_sid: "articuno31", twilio_enabled: true) + admin = create(:casa_admin, casa_org: org) + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("supervisor") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("supervisor") + @short_io_stub = WebMockHelper.short_io_stub_sms sign_in admin diff --git a/spec/requests/volunteers_spec.rb b/spec/requests/volunteers_spec.rb index 21f44e6c42..0b2c66c6e6 100644 --- a/spec/requests/volunteers_spec.rb +++ b/spec/requests/volunteers_spec.rb @@ -112,13 +112,6 @@ end describe "POST /create" do - before do - sign_in admin - @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("volunteer") - @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("volunteer") - @short_io_stub = WebMockHelper.short_io_stub_sms - end - context "with valid params" do let(:params) do { @@ -129,28 +122,34 @@ } end - it "creates a new volunteer" do - post volunteers_url, params: params + it "creates a new volunteer and sends account_setup email" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + + sign_in admin + expect { + post volunteers_url, params: params + }.to change { ActionMailer::Base.deliveries.count }.by(1) + expect(response).to have_http_status(:redirect) volunteer = Volunteer.last expect(volunteer.email).to eq("volunteer1@example.com") expect(volunteer.display_name).to eq("Example") + expect(volunteer.casa_org).to eq(admin.casa_org) expect(response).to redirect_to edit_volunteer_path(volunteer) end - it "assigns new volunteer to creator's organization" do - expect(volunteer.casa_org_id).to eq(admin.casa_org_id) - end - - it "sends an account_setup email" do - expect { - post volunteers_url, params: params - }.to change { ActionMailer::Base.deliveries.count }.by(1) - end - it "sends a SMS when phone number exists" do + organization = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: organization) params[:volunteer][:phone_number] = "+12222222222" + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("volunteer") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("volunteer") + @short_io_stub = WebMockHelper.short_io_stub_sms + + sign_in admin post volunteers_url, params: params + expect(@short_io_stub).to have_been_requested.times(2) expect(@twilio_activation_success_stub).to have_been_requested.times(1) expect(response).to have_http_status(:redirect) @@ -159,7 +158,15 @@ end it "does not send a SMS when phone number is not provided" do + organization = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: organization) + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("volunteer") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("volunteer") + @short_io_stub = WebMockHelper.short_io_stub_sms + + sign_in admin post volunteers_url, params: params + expect(@short_io_stub).to have_been_requested.times(0) expect(@twilio_activation_success_stub).to have_been_requested.times(0) expect(@twilio_activation_error_stub).to have_been_requested.times(0) @@ -169,13 +176,16 @@ end it "does not send a SMS when Twilio API has an error" do - org = create(:casa_org, twilio_account_sid: "articuno31") - admin = build(:casa_admin, casa_org: org) + org = create(:casa_org, twilio_account_sid: "articuno31", twilio_enabled: true) + admin = create(:casa_admin, casa_org: org) + @twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub("volunteer") + @twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub("volunteer") + @short_io_stub = WebMockHelper.short_io_stub_sms + params[:volunteer][:phone_number] = "+12222222222" sign_in admin - - params[:volunteer][:phone_number] = "+12222222222" post volunteers_url, params: params + expect(@twilio_activation_error_stub).to have_been_requested.times(1) expect(response).to have_http_status(:redirect) follow_redirect! @@ -185,11 +195,11 @@ it "does not send a SMS if the casa_org does not have Twilio enabled" do org = create(:casa_org, twilio_enabled: false) admin = build(:casa_admin, casa_org: org) + params[:volunteer][:phone_number] = "+12222222222" sign_in admin - - params[:volunteer][:phone_number] = "+12222222222" post volunteers_url, params: params + expect(response).to have_http_status(:redirect) follow_redirect! expect(flash[:notice]).to match(/New volunteer created successfully./) @@ -207,16 +217,16 @@ end it "does not create a new volunteer" do - expect { - post volunteers_url, params: params - }.to_not change { Volunteer.count } - expect(response).to have_http_status(:success) - end + org = create(:casa_org, twilio_enabled: false) + admin = build(:casa_admin, casa_org: org) + + sign_in admin - it "sends an account_setup email" do expect { post volunteers_url, params: params - }.to_not change { ActionMailer::Base.deliveries.count } + }.to change(Volunteer, :count).by(0) + .and change(ActionMailer::Base.deliveries, :count).by(0) + expect(response).to have_http_status(:success) end end end diff --git a/spec/services/court_report_due_sms_reminder_service_spec.rb b/spec/services/court_report_due_sms_reminder_service_spec.rb index 79d972cabc..c60809c24e 100644 --- a/spec/services/court_report_due_sms_reminder_service_spec.rb +++ b/spec/services/court_report_due_sms_reminder_service_spec.rb @@ -5,7 +5,8 @@ include SmsBodyHelper describe "court report due sms reminder service" do - let!(:volunteer) { create(:volunteer, receive_sms_notifications: true, phone_number: "+12223334444") } + let(:org) { create(:casa_org, twilio_enabled: true) } + let!(:volunteer) { create(:volunteer, casa_org: org, receive_sms_notifications: true, phone_number: "+12223334444") } let!(:report_due_date) { Date.current + 7.days } before :each do diff --git a/spec/services/learning_hours_export_csv_service_spec.rb b/spec/services/learning_hours_export_csv_service_spec.rb index b482360fdb..f0b092bbb6 100644 --- a/spec/services/learning_hours_export_csv_service_spec.rb +++ b/spec/services/learning_hours_export_csv_service_spec.rb @@ -2,12 +2,13 @@ RSpec.describe LearningHoursExportCsvService do let!(:user) { create(:user) } - + let!(:learning_hour_type) { create(:learning_hour_type) } let!(:learning_hour) do create(:learning_hour, duration_hours: 2, duration_minutes: 30, - occurred_at: "2022-06-20") + occurred_at: "2022-06-20", + learning_hour_type: learning_hour_type) end describe "#perform" do @@ -20,7 +21,8 @@ end it "returns a csv as a string ending with the learning hours values" do - csv_values = "#{user.display_name},#{learning_hour.name},#{learning_hour.learning_type},2:30,2022-06-20\n" + csv_values = "#{user.display_name},#{learning_hour.name},#{learning_hour.learning_hour_type.name}," \ + "2:30,2022-06-20\n" if learning_hour.name.include? "," csv_values.gsub!(learning_hour.name, '"' + learning_hour.name + '"') diff --git a/spec/services/no_contact_made_sms_reminder_service_spec.rb b/spec/services/no_contact_made_sms_reminder_service_spec.rb index 1125366b60..4a04ab2781 100644 --- a/spec/services/no_contact_made_sms_reminder_service_spec.rb +++ b/spec/services/no_contact_made_sms_reminder_service_spec.rb @@ -5,7 +5,8 @@ include SmsBodyHelper describe "court report due sms reminder service" do - let!(:volunteer) { create(:volunteer, receive_sms_notifications: true, phone_number: "+12222222222") } + let(:org) { create(:casa_org, twilio_enabled: true) } + let!(:volunteer) { create(:volunteer, receive_sms_notifications: true, phone_number: "+12222222222", casa_org: org) } let!(:contact_type) { "test" } before :each do diff --git a/spec/services/preference_set_table_state_service_spec.rb b/spec/services/preference_set_table_state_service_spec.rb new file mode 100644 index 0000000000..b8efe534e7 --- /dev/null +++ b/spec/services/preference_set_table_state_service_spec.rb @@ -0,0 +1,60 @@ +require "rails_helper" + +RSpec.describe PreferenceSetTableStateService do + subject { described_class.new(user_id: user.id) } + + let!(:user) { create(:user) } + let(:preference_set) { user.preference_set } + let!(:table_state) { {"volunteers_table" => {"columns" => [{"visible" => true}]}} } + let(:table_state2) { {"columns" => [{"visible" => false}]} } + let(:table_name) { "volunteers_table" } + + describe "#update!" do + context "when the update is successful" do + it "updates the table state" do + expect { + subject.update!(table_state: table_state2, table_name: table_name) + }.to change { + preference_set.reload.table_state + }.from({}).to({table_name => table_state2}) + end + end + + context "when the update fails" do + before do + allow_any_instance_of(PreferenceSet).to receive(:save!).and_raise(ActiveRecord::RecordNotSaved) + end + + it "raises an error" do + expect { + subject.update!(table_state: table_state2, table_name: table_name) + }.to raise_error(PreferenceSetTableStateService::TableStateUpdateFailed, "Failed to update table state for '#{table_name}'") + end + end + end + + describe "#table_state" do + context "when the preference set exists" do + before do + table_state = {"columns" => [{"visible" => true}]} + user.preference_set.table_state["volunteers_table"] = table_state + user.preference_set.save! + end + + it "returns the table state" do + expect(subject.table_state(table_name: "volunteers_table")).to eq(table_state["volunteers_table"]) + end + end + + context "when there is no data for that table name" do + before do + preference_set.table_state = {} + preference_set.save + end + + it "returns nil" do + expect(subject.table_state(table_name: table_name)).to eq(nil) + end + end + end +end diff --git a/spec/services/sms_reminder_service_spec.rb b/spec/services/sms_reminder_service_spec.rb index c6b6074569..c396255eac 100644 --- a/spec/services/sms_reminder_service_spec.rb +++ b/spec/services/sms_reminder_service_spec.rb @@ -3,7 +3,8 @@ RSpec.describe SmsReminderService do describe "court report due sms reminder service" do - let!(:volunteer) { create(:volunteer, receive_sms_notifications: true, phone_number: "+12222222222") } + let(:org) { create(:casa_org, twilio_enabled: true) } + let!(:volunteer) { create(:volunteer, casa_org: org, receive_sms_notifications: true, phone_number: "+12222222222") } let!(:message) { "It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF" } before :each do diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 65bf0f8342..cb97c36833 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -14,7 +14,7 @@ options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--disable-gpu") options.add_argument("--ignore-certificate-errors") -options.add_argument("--window-size=1280,900") +options.add_argument("--window-size=1280,1900") options.add_preference(:browser, set_download_behavior: {behavior: "allow"}) @@ -47,7 +47,6 @@ config.before(:each, type: :system, js: true) do config.include DownloadHelpers - config.include CsvExporterHelper clear_downloads if ENV["DOCKER"] driven_by :selenium_chrome_headless_in_container @@ -58,4 +57,17 @@ driven_by :selenium_chrome_headless end end + + config.before(:each, type: :system, debug: true) do + config.include DownloadHelpers + clear_downloads + if ENV["DOCKER"] + driven_by :selenium_chrome_in_container + Capybara.server_host = "0.0.0.0" + Capybara.server_port = 4000 + Capybara.app_host = "http://web:4000" + else + driven_by :selenium_chrome + end + end end diff --git a/spec/support/csv_exporter_helper.rb b/spec/support/csv_exporter_helper.rb deleted file mode 100644 index e44357939f..0000000000 --- a/spec/support/csv_exporter_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module CsvExporterHelper - TIMEOUT = 10 - - def wait_for_csv_parse(model, result, fields) - Timeout.timeout(TIMEOUT) do - sleep 0.025 while not_include_fields?(model, result, fields) - end - end - - def not_include_fields?(model, result, fields) - fields.collect do |f| - result.include?(model.send(f)) - end.flatten.compact.uniq.include?(false) - end -end diff --git a/spec/support/docx_inspector.rb b/spec/support/docx_inspector.rb deleted file mode 100644 index 7e1efd4b6d..0000000000 --- a/spec/support/docx_inspector.rb +++ /dev/null @@ -1,247 +0,0 @@ -class DocxInspector - IGNORED_FILE_LIST = {"fontTable" => 0, "numbering" => 0, "settings" => 0, "styles" => 0, "webSettings" => 0} - DOCX_WORD_DIRECTORY_FILENAME_CAPTURE_PATTERN = /^word\/([^\/]*)\.xml/ # Capture the file name of a file in the docx's word/ directory (not recursive) - - def initialize(docx_contents: nil, docx_path: nil) - if !docx_contents.nil? - docx_as_zip = get_docx_as_zip_object(docx_contents: docx_contents) - elsif !docx_path.nil? - docx_as_zip = get_docx_as_zip_object(docx_path: docx_path) - else - raise ArgumentError.new("Insufficient parameters. Either docx_contents or docx_path is required.") - end - - @word_lists_by_document_section = {document: [], endnotes: [], footnotes: [], footer: [], header: []} - - get_docx_readable_text_XML_files(docx_as_zip).each do |file| - file_name = file.name.match(DOCX_WORD_DIRECTORY_FILENAME_CAPTURE_PATTERN).captures[0] - viewable_strings = get_displayed_text_list(get_XML_object(file)) - - case file_name - when /^document/ - @word_lists_by_document_section[:document].concat(viewable_strings) - when /^endnotes/ - @word_lists_by_document_section[:endnotes].concat(viewable_strings) - when /^footnotes/ - @word_lists_by_document_section[:footnotes].concat(viewable_strings) - when /^footer/ - @word_lists_by_document_section[:footer].concat(viewable_strings) - when /^header/ - @word_lists_by_document_section[:header].concat(viewable_strings) - end - end - - @unsorted_word_lists_by_document_section = @word_lists_by_document_section.deep_dup - - @word_lists_by_document_section.each do |section, word_list| - sort_string_list_by_length_ascending(word_list) - end - end - - def get_word_list_all(sorted: true) - word_lists = if sorted - @word_lists_by_document_section - else - @unsorted_word_lists_by_document_section - end - - all_words_list = word_lists[:document] + - word_lists[:endnotes] + - word_lists[:footnotes] + - word_lists[:footer] + - word_lists[:header] - - sort_string_list_by_length_ascending(all_words_list) unless !sorted - - all_words_list - end - - def get_word_list_document(sorted: true) - if sorted - @word_lists_by_document_section[:document] - else - @unsorted_word_lists_by_document_section[:document] - end - end - - def get_word_list_endnotes(sorted: true) - if sorted - @word_lists_by_document_section[:endnotes] - else - @unsorted_word_lists_by_document_section[:endnotes] - end - end - - def get_word_list_footnotes(sorted: true) - if sorted - @word_lists_by_document_section[:footnotes] - else - @unsorted_word_lists_by_document_section[:footnotes] - end - end - - def get_word_list_footer(sorted: true) - if sorted - @word_lists_by_document_section[:footer] - else - @unsorted_word_lists_by_document_section[:footer] - end - end - - def get_word_list_header(sorted: true) - if sorted - @word_lists_by_document_section[:header] - else - @unsorted_word_lists_by_document_section[:header] - end - end - - def word_list_all_contains?(str) - word_list_contains_str?(get_word_list_all, str) - end - - def word_list_document_contains?(str) - word_list_contains_str?(get_word_list_document, str) - end - - def word_list_endnotes_contains?(str) - word_list_contains_str?(get_word_list_endnotes, str) - end - - def word_list_footnotes_contains?(str) - word_list_contains_str?(get_word_list_footnotes, str) - end - - def word_list_footer_contains?(str) - word_list_contains_str?(get_word_list_footer, str) - end - - def word_list_header_contains?(str) - word_list_contains_str?(get_word_list_header, str) - end - - def word_list_all_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_all(sorted: false), str_list) - end - - def word_list_document_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_document(sorted: false), str_list) - end - - def word_list_endnotes_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_endnotes(sorted: false), str_list) - end - - def word_list_footnotes_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_footnotes(sorted: false), str_list) - end - - def word_list_footer_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_footer(sorted: false), str_list) - end - - def word_list_header_contains_in_order?(str_list) - word_list_contains_str_in_order?(get_word_list_header(sorted: false), str_list) - end - - private - - def get_displayed_text_list(xml_object) - xml_object.xpath("//w:t/text()").filter_map do |word_text_element| - stripped_text = word_text_element.text.strip - stripped_text if stripped_text.length > 0 - end - end - - def get_docx_as_zip_object(docx_contents: nil, docx_path: nil) - if !docx_contents.nil? - Zip::File.open_buffer(docx_contents) - elsif !docx_path.nil? - Zip::File.open(docx_path) - else - raise ArgumentError.new("Insufficient parameters. Either docx_contents or docx_path is required.") - end - end - - def get_docx_readable_text_XML_files(docx_as_zip) - docx_as_zip.entries.select do |entry| - entry_name = entry.name - is_ignored_file = false - xml_file_in_word_match = entry_name.match(DOCX_WORD_DIRECTORY_FILENAME_CAPTURE_PATTERN) - - unless xml_file_in_word_match.nil? - xml_file_name = xml_file_in_word_match.captures[0] - is_ignored_file = !IGNORED_FILE_LIST[xml_file_name].nil? - end - - !(xml_file_in_word_match.nil? || is_ignored_file) - end - end - - def get_XML_object(xml_file_as_docx_zip_entry) - Nokogiri::XML(xml_file_as_docx_zip_entry.get_input_stream.read) - end - - def search_string_list_for_index_of_first_string_of_at_least_n_length(string_list_sorted_by_length, n) - low = 0 - high = string_list_sorted_by_length.length - 1 - mid = (low + high) / 2 - - while low < high - if string_list_sorted_by_length[mid].length < n - low = mid + 1 - else - high = mid - 1 - end - - mid = (low + high) / 2 - end - - if string_list_sorted_by_length[mid].length < n - if string_list_sorted_by_length.length - 1 == mid - return nil - else - return mid + 1 - end - end - - [0, mid].max - end - - def sort_string_list_by_length_ascending(str_list) - str_list.sort_by!(&:length) - end - - def word_list_contains_str?(word_list, str) - first_possible_word_containing_str_index = search_string_list_for_index_of_first_string_of_at_least_n_length( - word_list, - str.length - ) - - if first_possible_word_containing_str_index.nil? - return false - end - - word_list[first_possible_word_containing_str_index..(word_list.length - 1)].each do |word| - if word.include?(str) - return true - end - end - - false - end - - def word_list_contains_str_in_order?(word_list, str_list) - return true unless str_list.present? - - str_index = 0 - word_list.each do |word| - if word.include?(str_list[str_index]) - str_index += 1 - return true if str_index == str_list.length - end - end - - false - end -end diff --git a/spec/support/download_helpers.rb b/spec/support/download_helpers.rb index b947f8b07e..9b9fd85424 100644 --- a/spec/support/download_helpers.rb +++ b/spec/support/download_helpers.rb @@ -15,6 +15,26 @@ def download_content File.read(download) end + def download_docx + wait_for_download + Docx::Document.open(download) + end + + def header_text(download_docx) + zip = download_docx.zip + files = zip.glob("word/header*.xml").map { |h| h.name } + filename_and_contents_pairs = files.map do |file| + simple_file_name = file.sub(/^word\//, "").sub(/\.xml$/, "") + [simple_file_name, Nokogiri::XML(zip.read(file))] + end + + filename_and_contents_pairs.map { |name, doc| doc.text }.join("\n") + end + + def table_text(download_docx) + download_docx.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten + end + def download_file_name File.basename(download) end diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb index 6d23e10fdb..f509b93dc8 100644 --- a/spec/support/factory_bot.rb +++ b/spec/support/factory_bot.rb @@ -47,7 +47,9 @@ def upload_file(file) end config.after(:suite) do - puts "How many objects did factory_bot create? (probably too many- let's tune some factories...)" - pp factory_bot_results + if ENV.fetch("SPEC_OUTPUT_FACTORY_BOT_OBJECT_CREATION_STATS", "false") == "true" + puts "How many objects did factory_bot create? (probably too many- let's tune some factories...)" + pp factory_bot_results + end end end diff --git a/spec/system/banners/banners_spec.rb b/spec/system/banners/banners_spec.rb new file mode 100644 index 0000000000..00f015707f --- /dev/null +++ b/spec/system/banners/banners_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe "Banners", type: :system, js: true do + let(:admin) { create(:casa_admin) } + let(:organization) { admin.casa_org } + + it "add a banner" do + sign_in admin + + visit banners_path + click_on "New Banner" + fill_in "Name", with: "Volunteer Survey Announcement" + check "Active?" + fill_in_rich_text_area "banner_content", with: "Please fill out this survey." + click_on "Submit" + + visit banners_path + expect(page).to have_text("Volunteer Survey Announcement") + + visit banners_path + within "#banners" do + click_on "Edit", match: :first + end + fill_in "Name", with: "Better Volunteer Survey Announcement" + click_on "Submit" + + visit banners_path + expect(page).to have_text("Better Volunteer Survey Announcement") + + visit root_path + expect(page).to have_text("Please fill out this survey.") + end +end diff --git a/spec/system/bulk_court_dates/new_spec.rb b/spec/system/bulk_court_dates/new_spec.rb new file mode 100644 index 0000000000..80a3b56dc7 --- /dev/null +++ b/spec/system/bulk_court_dates/new_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "bulk_court_dates/new", type: :system do + let(:now) { Date.new(2021, 1, 1) } + let(:casa_org) { create(:casa_org) } + let(:admin) { create(:casa_admin, casa_org: casa_org) } + let!(:casa_case) { create(:casa_case, casa_org: casa_org) } + let!(:court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now - 1.week) } + let!(:judge) { create(:judge) } + let!(:hearing_type) { create(:hearing_type) } + let(:court_order_text) { Faker::Lorem.paragraph(sentence_count: 2) } + + it "is successful", js: true do + case_group = build(:case_group, casa_org: casa_org) + case_group.case_group_memberships.first.casa_case = casa_case + case_group.save! + + travel_to now + sign_in admin + visit casa_cases_path + click_on "New Bulk Court Date" + + select case_group.name, from: "Case Group" + fill_in "court_date_date", with: :now + fill_in "court_date_court_report_due_date", with: :now + select judge.name, from: "Judge" + select hearing_type.name, from: "Hearing type" + + click_on "Add a court order" + text_area = first(:css, "textarea").native + text_area.send_keys(court_order_text) + page.find("select.implementation-status").find(:option, text: "Partially implemented").select_option + + within ".top-page-actions" do + click_on "Create" + end + + visit casa_case_path(casa_case) + expect(page).to have_content(hearing_type.name) + expect(page).to have_content(court_order_text) + end +end diff --git a/spec/system/casa_admins/edit_spec.rb b/spec/system/casa_admins/edit_spec.rb index 8e10d6c1c3..5d5e5ad30c 100644 --- a/spec/system/casa_admins/edit_spec.rb +++ b/spec/system/casa_admins/edit_spec.rb @@ -103,10 +103,6 @@ click_on "Resend Invitation" expect(page).to have_content("Invitation sent") - - deliveries = ActionMailer::Base.deliveries - expect(deliveries.count).to eq(1) - expect(deliveries.last.subject).to have_text "CASA Console invitation instructions" end it "can convert the admin to a supervisor", js: true do diff --git a/spec/system/casa_cases/additional_index_spec.rb b/spec/system/casa_cases/additional_index_spec.rb index d2b9927f24..45c4ecd4e1 100644 --- a/spec/system/casa_cases/additional_index_spec.rb +++ b/spec/system/casa_cases/additional_index_spec.rb @@ -24,7 +24,6 @@ expect(page).to have_selector("th", text: "Status") expect(page).to have_selector("th", text: "Transition Aged Youth") expect(page).to have_selector("th", text: "Assigned To") - expect(page).to have_selector("th", text: "Actions") end it "filters active/inactive", js: true do diff --git a/spec/system/casa_cases/edit_spec.rb b/spec/system/casa_cases/edit_spec.rb index 42a351b299..2ba190d90d 100644 --- a/spec/system/casa_cases/edit_spec.rb +++ b/spec/system/casa_cases/edit_spec.rb @@ -41,7 +41,7 @@ select "Submitted", from: "casa_case_court_report_status" check contact_type.name - page.find("#add-court-order-button").click + page.find('button[data-action="extended-nested-form#add"]').click find("#court-orders-list-container").first("textarea").send_keys("Court Order Text One") within ".top-page-actions" do @@ -211,17 +211,15 @@ expect(page).to have_text("Court Report Status: Not submitted") visit edit_casa_case_path(casa_case) select "Submitted", from: "casa_case_court_report_status" - check "Youth" - - # fill_in "Court Report Due Date", with: Date.new(next_year.to_i, 9, 8).strftime("%Y/%m/%d\n") - page.find("#add-court-order-button").click + scroll_to('button[data-action="extended-nested-form#add"]').click find("#court-orders-list-container").first("textarea").send_keys("Court Order Text One") select "Partially implemented", from: "casa_case[case_court_orders_attributes][0][implementation_status]" expect(page).to have_text("Set Implementation Status") + check "Youth" within ".actions-cc" do click_on "Update CASA Case" end @@ -389,13 +387,11 @@ def sign_in_and_assign_volunteer expect(page).to have_text(text) - find("button.remove-court-order-button").click + find('button[data-action="click->extended-nested-form#remove"]').click expect(page).to have_text("Are you sure you want to remove this court order? Doing so will delete all records \ of it unless it was included in a previous court report.") find("button.swal2-confirm").click - expect(page).to have_text("Court order has been removed.") - click_on "OK" expect(page).to_not have_text(text) within ".actions-cc" do @@ -549,7 +545,7 @@ def sign_in_and_assign_volunteer expect(page).not_to have_text("Youth's Date in Care") expect(page).not_to have_text("Deactivate Case") - expect(page).to have_css("#add-court-order-button") + expect(page).to have_css('button[data-action="extended-nested-form#add"]') visit casa_case_path(casa_case) expect(page).to have_text("Court Report Status: Submitted") diff --git a/spec/system/casa_cases/index_spec.rb b/spec/system/casa_cases/index_spec.rb index a4e3c7f44c..819a55166e 100644 --- a/spec/system/casa_cases/index_spec.rb +++ b/spec/system/casa_cases/index_spec.rb @@ -1,15 +1,15 @@ require "rails_helper" RSpec.describe "casa_cases/index", type: :system do - let(:organization) { build(:casa_org) } - let(:admin) { build(:casa_admin, casa_org: organization) } - let(:volunteer) { build(:volunteer, display_name: "Cool Volunteer", casa_org: organization) } - let(:cina) { build(:casa_case, active: true, casa_org: organization, case_number: "CINA-1") } - let!(:tpr) { create(:casa_case, active: true, casa_org: organization, case_number: "TPR-100") } - let!(:no_prefix) { create(:casa_case, active: true, casa_org: organization, case_number: "123-12-123") } - let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: cina) } + it "is filterable and linkable", :js do + organization = build(:casa_org) + admin = build(:casa_admin, casa_org: organization) + volunteer = build(:volunteer, display_name: "Cool Volunteer", casa_org: organization) + cina = build(:casa_case, active: true, casa_org: organization, case_number: "CINA-1") + tpr = create(:casa_case, active: true, casa_org: organization, case_number: "TPR-100") + no_prefix = create(:casa_case, active: true, casa_org: organization, case_number: "123-12-123") + create(:case_assignment, volunteer: volunteer, casa_case: cina) - scenario "filterable and linkable", :js do sign_in admin visit casa_cases_path diff --git a/spec/system/casa_cases/new_spec.rb b/spec/system/casa_cases/new_spec.rb index 265486c10c..f55f21e00b 100644 --- a/spec/system/casa_cases/new_spec.rb +++ b/spec/system/casa_cases/new_spec.rb @@ -1,26 +1,22 @@ require "rails_helper" RSpec.describe "casa_cases/new", type: :system do - let(:casa_org) { build(:casa_org) } - let(:admin) { create(:casa_admin, casa_org: casa_org) } - let(:supervisor) { create(:supervisor, casa_org: casa_org) } - let(:case_number) { "12345" } - let!(:next_year) { (Date.today.year + 1).to_s } - let(:court_date) { 21.days.from_now } - let(:contact_type_group) { create(:contact_type_group, casa_org: casa_org) } - let!(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) } - context "when signed in as a Casa Org Admin" do - before do - sign_in admin - visit root_path - click_on "Cases" - - click_on "New Case" - end - context "when all fields are filled" do it "is successful", js: true do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + contact_type_group = create(:contact_type_group, casa_org: casa_org) + contact_type = create(:contact_type, contact_type_group: contact_type_group) + case_number = "12345" + court_date = 21.days.from_now + + sign_in admin + visit root_path + + click_on "Cases" + click_on "New Case" + travel_to Time.zone.local(2020, 12, 1) do fourteen_years = (Date.today.year - 14).to_s fill_in "Case number", with: case_number @@ -32,13 +28,14 @@ select "Submitted", from: "casa_case_court_report_status" + expect(page).to have_content(contact_type_group.name) check contact_type.name within ".top-page-actions" do click_on "Create CASA Case" end - expect(page.body).to have_content(case_number) + expect(page).to have_content(case_number) expect(page).to have_content(I18n.l(court_date, format: :day_and_date)) expect(page).to have_content("CASA case was successfully created.") expect(page).not_to have_content("Court Report Due Date: Thursday, 1-APR-2021") # accurate for frozen time @@ -49,6 +46,15 @@ context "when non-mandatory fields are not filled" do it "is successful" do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + contact_type_group = create(:contact_type_group, casa_org: casa_org) + create(:contact_type, contact_type_group: contact_type_group) + case_number = "12345" + + sign_in admin + visit new_casa_case_path + fill_in "Case number", with: case_number fill_in "Next Court Date", with: DateTime.now.next_month.strftime("%Y/%m/%d") five_years = (Date.today.year - 5).to_s @@ -69,6 +75,12 @@ context "when the case number field is not filled" do it "does not create a new case" do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + + sign_in admin + visit new_casa_case_path + within ".actions-cc" do click_on "Create CASA Case" end @@ -81,6 +93,15 @@ context "when the court date field is not filled" do context "when empty court date checkbox is checked" do it "creates a new case" do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + contact_type_group = create(:contact_type_group, casa_org: casa_org) + create(:contact_type, contact_type_group: contact_type_group) + case_number = "12345" + + sign_in admin + visit new_casa_case_path + fill_in "Case number", with: case_number five_years = (Date.today.year - 5).to_s select "March", from: "casa_case_birth_month_year_youth_2i" @@ -101,6 +122,13 @@ context "when empty court date checkbox is not checked" do it "does not create a new case" do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + case_number = "12345" + + sign_in admin + visit new_casa_case_path + fill_in "Case number", with: case_number five_years = (Date.today.year - 5).to_s select "March", from: "casa_case_birth_month_year_youth_2i" @@ -117,9 +145,15 @@ end context "when the case number already exists in the organization" do - let!(:casa_case) { create(:casa_case, case_number: case_number, casa_org: casa_org) } - it "does not create a new case" do + casa_org = build(:casa_org) + admin = create(:casa_admin, casa_org: casa_org) + case_number = "12345" + _existing_casa_case = create(:casa_case, case_number: case_number, casa_org: casa_org) + + sign_in admin + visit new_casa_case_path + fill_in "Case number", with: case_number within ".actions-cc" do click_on "Create CASA Case" @@ -129,38 +163,5 @@ expect(page).to have_content("Case number has already been taken") end end - - context "contact types" do - let(:contact_type_group) { create(:contact_type_group, casa_org: casa_org) } - let!(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) } - - it "are shown in groups" do - visit new_casa_case_path - - expect(page).to have_content(contact_type.name) - expect(page).to have_content(contact_type_group.name) - end - end - - context "when trying to assign a volunteer to a case" do - it "should not be able to assign volunteers" do - visit new_casa_case_path - - expect(page).not_to have_content("Manage Volunteers") - expect(page).not_to have_css("#volunteer-assignment") - end - end - end - - context "when signed in as a supervisor" do - before do - sign_in supervisor - visit root_path - click_on "Cases" - end - - it "should not provide option to make new case" do - expect(page).not_to have_button("New Case") - end end end diff --git a/spec/system/casa_org/edit_spec.rb b/spec/system/casa_org/edit_spec.rb index a51f2a205d..682e87743a 100644 --- a/spec/system/casa_org/edit_spec.rb +++ b/spec/system/casa_org/edit_spec.rb @@ -1,104 +1,13 @@ require "rails_helper" RSpec.describe "casa_org/edit", type: :system do - let(:organization) { build(:casa_org) } - let!(:languages) do - 5.times { create(:language, name: Faker::Nation.unique.language, casa_org: organization) } - end - let(:admin) { build(:casa_admin, casa_org_id: organization.id) } - let!(:contact_type_group) { create(:contact_type_group, casa_org: organization, name: "Contact type group 1") } - let!(:contact_type) { create(:contact_type, name: "Contact type 1") } - let!(:hearing_type) { create(:hearing_type, casa_org: organization, name: "Spec Test Hearing Type") } - let!(:judge) { create(:judge, casa_org: organization, name: "Joey Tom") } - let!(:sent_email) { create(:sent_email, casa_org: organization, user: admin) } - - before do - stub_twillio - sign_in admin - visit edit_casa_org_path(organization) - Faker::Nation.unique.clear # Clears used values for Faker::Nation - end - - it "loads casa org edit page" do - expect(page).to have_text "Editing CASA Organization" - expect(page).to_not have_text "sign in before continuing" - end - - it "has contact type groups content" do - expect(page).to have_text("Contact type group 1") - expect(page).to have_text(contact_type_group.name) - end - - it "has contact type groups table", js: true do - scroll_to(page.find("table#contact-type-groups", visible: false)) - expect(page).to have_table( - id: "contact-type-groups", - with_rows: - [ - ["Contact type group 1", "Yes", "Edit"] - ] - ) - end - - it "has contact types content" do - expect(page).to have_text("Contact type 1") - expect(page).to have_text(contact_type.name) - end - - it "has contact types table", js: true do - scroll_to(page.find("table#contact-types", visible: false)) - expect(page).to have_table( - id: "contact-types", - with_rows: - [ - ["Contact type 1", "Yes", "Edit"] - ] - ) - end - - it "has hearing types content" do - expect(page).to have_text("Spec Test Hearing Type") - expect(page).to have_text(hearing_type.name) - end - - it "has hearing types table", js: true do - scroll_to(page.find("table#hearing-types", visible: false)) - expect(page).to have_table( - id: "hearing-types", - with_rows: - [ - ["Spec Test Hearing Type", "Yes", "Edit"] - ] - ) - end - - it "has judge content" do - expect(page).to have_text(judge.name) - end - - it "has sent email content" do - expect(page).to have_text(sent_email.sent_address) - end - - it "has sent emails table", js: true do - travel_to DateTime.new(2021, 1, 2, 12, 30, 0) do - create(:sent_email, casa_org: organization, user: admin) - end + it "can update show_driving_reimbursement flag" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + sign_in admin visit edit_casa_org_path(organization) - scroll_to(page.find("table#sent-emails", visible: false)) - expect(page).to have_table(id: "sent-emails") - expect(page).to have_table( - id: "sent-emails", - with_rows: - [ - ["Mailer Type", "Mail Action Category", admin.email, "12:30pm 02 Jan 2021"] - ] - ) - end - - it "can update show_driving_reimbursement flag" do check "Show driving reimbursement" click_on "Submit" has_no_checked_field? "Show driving reimbursement" @@ -108,27 +17,44 @@ has_checked_field? "Show driving reimbursement" end - it "can upload a logo image", :aggregate_failure do + it "can upload a logo image" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + + stub_twilio + sign_in admin + visit edit_casa_org_path(organization) + page.attach_file("Logo", "spec/fixtures/company_logo.png", make_visible: true) - expect(organization.logo.attachment).to be_nil + expect(organization.logo).to_not be_attached click_on "Submit" - expect(organization.reload.logo.attachment).not_to be_nil + expect(organization.reload.logo).to be_attached end it "hides Twilio Form if twilio is not enabled", js: true do + organization = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: organization) + + sign_in admin + visit edit_casa_org_path(organization) + uncheck "Enable Twilio" - # Casa Org factory set to enable_twilio: true expect(page).to have_selector("#casa_org_twilio_account_sid", visible: :hidden) expect(page).to have_selector("#casa_org_twilio_api_key_sid", visible: :hidden) expect(page).to have_selector("#casa_org_twilio_api_key_secret", visible: :hidden) expect(page).to have_selector("#casa_org_twilio_phone_number", visible: :hidden) end - it "displays Twilio Form when Enable Twilio is checked", js: true do - # Casa Org factory set to enable_twilio: true + it "displays Twilio Form when Enable Twilio is checked" do + organization = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: organization) + + sign_in admin + visit edit_casa_org_path(organization) + expect(page).to have_text("Enable Twilio") expect(page).to have_selector("#casa_org_twilio_account_sid", visible: :visible) expect(page).to have_selector("#casa_org_twilio_api_key_sid", visible: :visible) @@ -137,43 +63,21 @@ end it "requires Twilio Form to be filled in correctly", js: true do + organization = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: organization) + + sign_in admin + visit edit_casa_org_path(organization) + fill_in "Twilio Phone Number", with: "" click_on "Submit" message = find("#casa_org_twilio_phone_number").native.attribute("validationMessage") expect(message).to eq "Please fill out this field." end - - describe "additional expense feature flag" do - context "enabled" do - before do - FeatureFlagService.enable!(FeatureFlagService::SHOW_ADDITIONAL_EXPENSES_FLAG) - visit edit_casa_org_path(organization) - end - - it "has option to enable additional expenses" do - expect(page).to have_text("Volunteers can add Other Expenses") - end - end - - context "disabled" do - before do - FeatureFlagService.disable!(FeatureFlagService::SHOW_ADDITIONAL_EXPENSES_FLAG) - visit edit_casa_org_path(organization) - end - - it "has option to enable additional expenses" do - expect(page).not_to have_text("Volunteers can add Other Expenses") - end - end - end - - it "requires name text field" do - expect(page).to have_selector("input[required=required]", id: "casa_org_name") - end end -def stub_twillio +def stub_twilio twillio_client = instance_double(Twilio::REST::Client) messages = instance_double(Twilio::REST::Api::V2010::AccountContext::MessageList) allow(Twilio::REST::Client).to receive(:new).with("Aladdin", "open sesame", "articuno34").and_return(twillio_client) diff --git a/spec/system/case_contacts/index_spec.rb b/spec/system/case_contacts/index_spec.rb index edf7d2d21a..87e42479d2 100644 --- a/spec/system/case_contacts/index_spec.rb +++ b/spec/system/case_contacts/index_spec.rb @@ -5,7 +5,8 @@ let(:organization) { create(:casa_org) } context "with case contacts" do - let(:casa_case) { build(:casa_case, casa_org: organization, case_number: "CINA-1") } + let(:case_number) { "CINA-1" } + let(:casa_case) { build(:casa_case, casa_org: organization, case_number: case_number) } let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) } context "without filter" do @@ -98,6 +99,63 @@ end end end + + it "can show only case contacts for one case" do + travel_to Date.new(2021, 1, 2) do + create(:case_contact, creator: volunteer, casa_case: casa_case, notes: "Case 1 Notes", occurred_at: Time.zone.yesterday - 1) + + another_case_number = "CINA-2" + another_case = create(:casa_case, casa_org: organization, case_number: another_case_number) + create(:case_assignment, volunteer: volunteer, casa_case: another_case) + create(:case_contact, creator: volunteer, casa_case: another_case, notes: "Case 2 Notes", occurred_at: Time.zone.today) + + sign_in volunteer + + # showing all cases + visit root_path + click_on "Case Contacts" + within "#ddmenu_case_contacts" do + click_on "All" + end + expect(page).to have_text("Case 1 Notes") + expect(page).to have_text("Case 2 Notes") + + # showing case 1 + visit root_path + click_on "Case Contacts" + within "#ddmenu_case_contacts" do + click_on case_number + end + expect(page).to have_text("Case 1 Notes") + expect(page).to_not have_text("Case 2 Notes") + + # showing case 2 + visit root_path + click_on "Case Contacts" + within "#ddmenu_case_contacts" do + click_on another_case_number + end + expect(page).to have_text("Case 2 Notes") + expect(page).to_not have_text("Case 1 Notes") + + # filtering to only show case 2 + click_button "Show / Hide" + fill_in "filterrific_occurred_starting_at", with: Time.zone.yesterday.to_s + fill_in "filterrific_occurred_ending_at", with: Time.zone.tomorrow.to_s + click_button "Filter" + expect(page).to have_text("Case 2 Notes") + expect(page).to_not have_text("Case 1 Notes") + + # no contacts because we're only showing case 1 and that occurred before the filter dates + visit root_path + click_on "Case Contacts" + within "#ddmenu_case_contacts" do + click_on case_number + end + expect(page).to_not have_text("Case 1 Notes") + expect(page).to_not have_text("Case 2 Notes") + end + end end context "without case contacts" do diff --git a/spec/system/case_contacts/new_spec.rb b/spec/system/case_contacts/new_spec.rb index 89e4339ebb..3b140fdf92 100644 --- a/spec/system/case_contacts/new_spec.rb +++ b/spec/system/case_contacts/new_spec.rb @@ -599,6 +599,22 @@ def index_of(text) end end + context "when driving reimbursement is hidden when volunteer not allowed to request" do + it "does not show for case_contacts" do + org = build(:casa_org, show_driving_reimbursement: true) + contact_type_group = create_contact_types(org) + volunteer = create(:volunteer, :with_disasllow_reimbursement, casa_org: org) + contact_types_for_cases = contact_type_group.contact_types.reject { |ct| ct.name == "Attorney" } + assign_contact_types_to_cases(volunteer.casa_cases, contact_types_for_cases) + + sign_in volunteer + + visit new_case_contact_path + + expect(page).not_to have_field("b. Want Driving Reimbursement") + end + end + describe "case default selection" do let(:volunteer) { create(:volunteer, :with_casa_cases) } let(:first_case) { volunteer.casa_cases.first } diff --git a/spec/system/case_groups/case_groups_spec.rb b/spec/system/case_groups/case_groups_spec.rb new file mode 100644 index 0000000000..032bb3c08c --- /dev/null +++ b/spec/system/case_groups/case_groups_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe "Case Groups", type: :system, js: true do + let(:admin) { create(:casa_admin) } + let(:organization) { admin.casa_org } + + it "create a case group" do + casa_case = create(:casa_case) + + sign_in admin + + visit case_groups_path + click_on "New Case Group" + fill_in "Name", with: "A family" + select casa_case.case_number, from: "Cases" + click_on "Submit" + + visit case_groups_path + expect(page).to have_text("A family") + + within "#case-groups" do + click_on "Edit", match: :first + end + fill_in "Name", with: "Another family" + click_on "Submit" + + visit case_groups_path + expect(page).to have_text("Another family") + end +end diff --git a/spec/system/court_dates/edit_spec.rb b/spec/system/court_dates/edit_spec.rb index 28b900b449..f3f37aa8b1 100644 --- a/spec/system/court_dates/edit_spec.rb +++ b/spec/system/court_dates/edit_spec.rb @@ -45,7 +45,7 @@ expect(page).to have_text("Court Orders - Please check that you didn't enter any youth names") expect(page).to have_text("Add a court order") - page.find("#add-court-order-button").click + page.find('button[data-action="extended-nested-form#add"]').click find("#court-orders-list-container").first("textarea").send_keys("Court Order Text One") within ".top-page-actions" do diff --git a/spec/system/court_dates/new_spec.rb b/spec/system/court_dates/new_spec.rb index 2b5a0b83e4..73d46c4b98 100644 --- a/spec/system/court_dates/new_spec.rb +++ b/spec/system/court_dates/new_spec.rb @@ -28,10 +28,10 @@ select judge.name, from: "Judge" select hearing_type.name, from: "Hearing type" - find("#add-court-order-button").click - - fill_in "court_date_case_court_orders_attributes_0_text", with: text - select "Partially implemented", from: "court_date_case_court_orders_attributes_0_implementation_status" + click_on "Add a court order" + text_area = first(:css, "textarea").native + text_area.send_keys(text) + page.find("select.implementation-status").find(:option, text: "Partially implemented").select_option within ".top-page-actions" do click_on "Create" diff --git a/spec/system/court_dates/view_spec.rb b/spec/system/court_dates/view_spec.rb new file mode 100644 index 0000000000..4dde0e9b1e --- /dev/null +++ b/spec/system/court_dates/view_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe "court_dates/edit", type: :system do + context "with date" + let(:now) { Date.new(2021, 1, 1) } + let(:organization) { create(:casa_org) } + let(:admin) { create(:casa_admin, casa_org: organization) } + let(:volunteer) { create(:volunteer) } + let(:supervisor) { create(:casa_admin, casa_org: organization) } + let!(:casa_case) { create(:casa_case, casa_org: organization) } + let!(:court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now - 1.week) } + let!(:future_court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now + 1.week) } + + before do + travel_to now + end + + context "as a volunteer" do + it "can download a report which focuses on the court date", js: true do + volunteer.casa_cases = [casa_case] + sign_in volunteer + + visit root_path + click_on "Cases" + click_on casa_case.case_number + + expect(CourtDate.count).to eq 2 + click_on "January 8, 2021" + expect(page).to have_content "Court Date" + + click_on "Download Report" + + wait_for_download + + expect(download_docx.paragraphs.map(&:to_s)).to include("Hearing Date: January 8, 2021") + end + end +end diff --git a/spec/system/dashboard/show_spec.rb b/spec/system/dashboard/show_spec.rb index fe0c63e1e8..8df2763c15 100644 --- a/spec/system/dashboard/show_spec.rb +++ b/spec/system/dashboard/show_spec.rb @@ -30,7 +30,6 @@ expect(page).to have_text("Bob Loblaw") expect(page).to have_no_link("Bob Loblaw") expect(page).to have_css("td", text: "Bob Loblaw") - expect(page).to have_link("Detail View", href: casa_case_path(casa_case.case_number.parameterize)) end it "displays 'No active cases' when they don't have any assignments", js: true do diff --git a/spec/system/notifications/index_spec.rb b/spec/system/notifications/index_spec.rb index f4ec9b955f..953c2fe9de 100644 --- a/spec/system/notifications/index_spec.rb +++ b/spec/system/notifications/index_spec.rb @@ -70,9 +70,9 @@ end it "lists followup notifications, showing their note" do - # Wait until page reloads - sleep(1) - expect(page).to have_content "Resolve Reminder" + within("#resolve", wait: 5) do + expect(page).to have_content "Resolve Reminder" + end sign_in volunteer visit notifications_path @@ -92,9 +92,9 @@ end it "lists followup notifications, showing the information in a single line when there are no notes" do - # Wait until page reloads - sleep(1) - expect(page).to have_content "Resolve Reminder" + within("#resolve", wait: 5) do + expect(page).to have_content "Resolve Reminder" + end sign_in volunteer visit notifications_path @@ -111,13 +111,14 @@ before do click_button "Make Reminder" - click_button "Confirm" end it "lists followup notifications showing admin current name" do - # Wait until page reloads - sleep(1) - expect(page).to have_content "Resolve Reminder" + click_button "Confirm" + + within("#resolve", wait: 5) do + expect(page).to have_content "Resolve Reminder" + end visit edit_users_path fill_in "Display name", with: created_by_name @@ -135,6 +136,36 @@ end end + context "EmancipationChecklistReminder" do + before do + volunteer.notifications << create(:notification, :emancipation_checklist_reminder, params: {casa_case: casa_case}) + sign_in volunteer + visit notifications_path + end + + it "should display a notification reminder that links to the emancipation checklist" do + notification_message = "Your case #{casa_case.case_number} is a transition aged youth. We want to make sure that along the way, we’re preparing our youth for emancipation. Make sure to check the emancipation checklist." + expect(page).not_to have_text(I18n.t(".notifications.index.no_notifications")) + expect(page).to have_content("Emancipation Checklist Reminder") + expect(page).to have_link(notification_message, href: casa_case_emancipation_path(casa_case.id)) + end + end + + context "YouthBirthdayNotification" do + before do + volunteer.notifications << create(:notification, :youth_birthday, params: {casa_case: casa_case}) + sign_in volunteer + visit notifications_path + end + + it "should display a notification on the notifications page" do + notification_message = "Your youth, case number: #{casa_case.case_number} has a birthday next month." + expect(page).not_to have_text(I18n.t(".notifications.index.no_notifications")) + expect(page).to have_content("Youth Birthday Notification") + expect(page).to have_link(notification_message, href: casa_case_path(casa_case.id)) + end + end + context "when there are no notifications" do it "displays a message to the user" do sign_in volunteer diff --git a/spec/system/reports/index_spec.rb b/spec/system/reports/index_spec.rb index 0f146ca40f..06a4e31950 100644 --- a/spec/system/reports/index_spec.rb +++ b/spec/system/reports/index_spec.rb @@ -1,25 +1,25 @@ require "rails_helper" RSpec.describe "reports", type: :system, js: true do - let!(:admin) { build(:casa_admin) } - let!(:case_contact) { build(:case_contact) } - - before do - sign_in user - visit reports_path - end - context "volunteer user" do - let(:user) { create(:volunteer) } - it "redirects to root" do + user = create(:volunteer) + + sign_in user + visit reports_path + expect(page).to_not have_text I18n.t("reports.index.reports_subhead") expect(page).to have_text "Sorry, you are not authorized to perform this action." end end - shared_examples "can view page" do + context "supervisor user" do it "renders form elements", js: true do + user = create(:supervisor) + + sign_in user + visit reports_path + expect(page).to have_text I18n.t("reports.index.reports_subhead") expect(page).to have_text I18n.l(6.months.ago.to_date, format: :day_and_date, default: "") expect(page).to have_text I18n.l(Date.today.to_date, format: :day_and_date, default: "") @@ -36,34 +36,46 @@ end it "downloads report", js: true do + user = create(:supervisor) + + sign_in user + visit reports_path + click_on I18n.t("reports.index.download_report_button") expect(page).to have_text("Downloading Report") end + end - it "downloads milesage report", js: true do - expect(page).to have_button I18n.t("reports.index.download_mileage_report_button.enabled") - - click_on I18n.t("reports.index.download_mileage_report_button.enabled") - - sleep 1 - - expect(page).to have_button I18n.t("reports.index.download_mileage_report_button.disabled"), disabled: true + context "casa_admin user" do + it "renders form elements", js: true do + user = create(:casa_admin) - sleep 3 + sign_in user + visit reports_path - expect(page).to have_button I18n.t("reports.index.download_mileage_report_button.enabled") + expect(page).to have_text I18n.t("reports.index.reports_subhead") + expect(page).to have_text I18n.l(6.months.ago.to_date, format: :day_and_date, default: "") + expect(page).to have_text I18n.l(Date.today.to_date, format: :day_and_date, default: "") + expect(page).to have_text I18n.t("reports.index.assigned_to_label") + expect(page).to have_text I18n.t("reports.index.volunteers_label") + expect(page).to have_text I18n.t("reports.index.contact_type_label") + expect(page).to have_text I18n.t("reports.index.contact_type_group_label") + expect(page).to have_text I18n.t("reports.index.driving_reimbursement_label") + expect(page).to have_text I18n.t("reports.index.contact_made_label") + expect(page).to have_text I18n.t("reports.index.transition_aged_label") + expect(page).to have_field(I18n.t("common.both_text"), count: 3) + expect(page).to have_field(I18n.t("common.yes_text"), count: 3) + expect(page).to have_field(I18n.t("common.no_text"), count: 3) end - end - context "supervisor user" do - it_behaves_like "can view page" do - let(:user) { create(:supervisor) } - end - end + it "downloads report", js: true do + user = create(:casa_admin) - context "casa_admin user" do - it_behaves_like "can view page" do - let(:user) { create(:casa_admin) } + sign_in user + visit reports_path + + click_on I18n.t("reports.index.download_report_button") + expect(page).to have_text("Downloading Report") end end end diff --git a/spec/system/static/index_spec.rb b/spec/system/static/index_spec.rb index b1d076fb54..24e94ef5bd 100644 --- a/spec/system/static/index_spec.rb +++ b/spec/system/static/index_spec.rb @@ -4,12 +4,13 @@ context "when visiting the CASA volunteer landing page", js: true do describe "when all organizations have logos" do before do - 3.times { create(:casa_org, :with_logo) } + 3.times { create(:casa_org, :with_logo, display_name: "CASA of Awesome") } visit root_path end it "has CASA organizations section" do expect(page).to have_text "CASA Organizations Powered by Our App" + expect(page).to have_text "CASA of Awesome" end it "displays all organizations that have attached logos" do @@ -21,14 +22,14 @@ describe "when some orgs are missing logos" do before do - 4.times { create(:casa_org, :with_logo) } - 4.times { create(:casa_org) } + create(:casa_org, :with_logo) + create(:casa_org) visit root_path end it "does not display organizations that don't have attached logos" do within("#organizations") do - expect(page).to have_css(".org_logo", count: 4) + expect(page).to have_css(".org_logo", count: 1) end end end diff --git a/spec/system/users/edit_spec.rb b/spec/system/users/edit_spec.rb index af3e198dec..3e71fc4e99 100644 --- a/spec/system/users/edit_spec.rb +++ b/spec/system/users/edit_spec.rb @@ -1,28 +1,14 @@ require "rails_helper" RSpec.describe "users/edit", type: :system do - let(:organization) { create(:casa_org) } - let(:volunteer) do - create( - :volunteer, - last_sign_in_at: "2020-01-01 00:00:00", - current_sign_in_at: "2020-01-02 00:00:00" - ) - end - let(:admin) { create(:casa_admin) } - let(:supervisor) { create(:supervisor) } - SmsNotificationEvent.delete_all - SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save - SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save - SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save - context "volunteer user" do - before do + it "displays password errors messages when user is unable to set a password with incorrect current password" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + sign_in volunteer visit edit_users_path - end - it "displays password errors messages when user is unable to set a password with incorrect current password" do click_on "Change Password" fill_in "current_password", with: "12345" @@ -35,6 +21,12 @@ end it "displays password errors messages when user is unable to set a password" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -48,12 +40,29 @@ end it "displays sms notification events for the volunteer user" do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, casa_org: organization) + + SmsNotificationEvent.delete_all + SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save + SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save + SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save + + sign_in volunteer + visit edit_users_path + expect(page).to have_content "sms_event_test_volunteer" expect(page).not_to have_content "sms_event_test_supervisor" expect(page).not_to have_content "sms_event_test_casa_admin" end it "notifies a user when they update their password" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -66,6 +75,12 @@ end it "notifies password changed by email", :aggregate_failures do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -83,6 +98,12 @@ end it "is able to send a confirmation email when Volunteer updates their email" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + click_on "Change Email" expect(page).to have_field("New Email", disabled: false) @@ -98,6 +119,12 @@ end it "displays email errors messages when user is unable to set a email with incorrect current password" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + click_on "Change Email" fill_in "current_password_email", with: "12345" @@ -109,6 +136,17 @@ end it "displays current sign in date" do + organization = create(:casa_org) + volunteer = create( + :volunteer, + casa_org: organization, + last_sign_in_at: "2020-01-01 00:00:00", + current_sign_in_at: "2020-01-02 00:00:00" + ) + + sign_in volunteer + visit edit_users_path + formatted_current_sign_in_at = I18n.l(volunteer.current_sign_in_at, format: :edit_profile, default: nil) formatted_last_sign_in_at = I18n.l(volunteer.last_sign_in_at, format: :edit_profile, default: nil) expect(page).to have_text("Last logged in #{formatted_current_sign_in_at}") @@ -116,6 +154,12 @@ end it "displays Volunteer error message if no communication preference is selected" do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + uncheck "user_receive_email_notifications" click_on "Save Preferences" expect(page).to have_content "1 error prohibited this Volunteer from being saved:" @@ -123,6 +167,12 @@ end it "displays Volunteer error message if SMS communication preference is selected without adding a valid phone number" do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, casa_org: organization) + + sign_in volunteer + visit edit_users_path + uncheck "user_receive_email_notifications" check "user_receive_sms_notifications" click_on "Save Preferences" @@ -130,38 +180,59 @@ expect(page).to have_text("Must add a valid phone number to receive SMS notifications.") end - it "displays notification events selection as enabled if sms notification preference is selected", js: true do + it "displays notification events selection as enabled if sms notification preference is selected" do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, casa_org: organization) + + SmsNotificationEvent.delete_all + SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save + SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save + SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save + + sign_in volunteer + visit edit_users_path + check "user_receive_sms_notifications" expect(page).to have_field("toggle-sms-notification-event", type: "checkbox", disabled: false) end it "displays notification events selection as disabled if sms notification preference is not selected", js: true do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, casa_org: organization) + + SmsNotificationEvent.delete_all + SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save + SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save + SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save + + sign_in volunteer + visit edit_users_path + uncheck "user_receive_sms_notifications" expect(page).to have_field("toggle-sms-notification-event", type: "checkbox", disabled: true) end end context "when a user's casa organization does not have twilio enabled" do - let(:organization_twilio_disabled) { create(:casa_org, twilio_enabled: false) } - let(:volunteer_twilio_disabled) { create(:volunteer, casa_org_id: organization_twilio_disabled.id) } + it "disables a users SMS communication checkbox" do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) - before do - sign_in volunteer_twilio_disabled + sign_in volunteer visit edit_users_path - end - it "disables a users SMS communication checkbox", js: true do expect(page).to have_field("Enable Twilio For Text Messaging", type: "checkbox", disabled: true) end end context "supervisor user" do - before do + it "notifies password changed by email", :aggregate_failures do + org = create(:casa_org) + supervisor = create(:supervisor, casa_org: org) + sign_in supervisor visit edit_users_path - end - it "notifies password changed by email", :aggregate_failures do click_on "Change Password" fill_in "current_password", with: "12345678" @@ -179,6 +250,12 @@ end it "is able to send a confrimation email when supervisor is updating email" do + org = create(:casa_org) + supervisor = create(:supervisor, casa_org: org) + + sign_in supervisor + visit edit_users_path + click_on "Change Email" expect(page).to have_field("New Email", disabled: false) @@ -194,6 +271,12 @@ end it "displays email errors messages when user is unable to set a email with incorrect current password" do + org = create(:casa_org) + supervisor = create(:supervisor, casa_org: org) + + sign_in supervisor + visit edit_users_path + click_on "Change Email" fill_in "current_password_email", with: "12345" @@ -205,12 +288,29 @@ end it "displays sms notification events for the supervisor user" do + org = create(:casa_org, twilio_enabled: true) + supervisor = create(:supervisor, casa_org: org) + + SmsNotificationEvent.delete_all + SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save + SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save + SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save + + sign_in supervisor + visit edit_users_path + expect(page).not_to have_content "sms_event_test_volunteer" expect(page).to have_content "sms_event_test_supervisor" expect(page).not_to have_content "sms_event_test_casa_admin" end it "displays Supervisor error message if no communication preference is selected" do + org = create(:casa_org) + supervisor = create(:supervisor, casa_org: org) + + sign_in supervisor + visit edit_users_path + uncheck "user_receive_email_notifications" click_on "Save Preferences" expect(page).to have_content "1 error prohibited this Supervisor from being saved:" @@ -218,6 +318,12 @@ end it "displays Supervisor error message if SMS communication preference is selected without adding a valid phone number" do + org = create(:casa_org, twilio_enabled: true) + supervisor = create(:supervisor, casa_org: org) + + sign_in supervisor + visit edit_users_path + uncheck "user_receive_email_notifications" check "user_receive_sms_notifications" click_on "Save Preferences" @@ -227,21 +333,77 @@ end context "when admin" do - let(:role) { "user" } - before do + it "is not able to update the profile without display name as an admin" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + sign_in admin visit edit_users_path - end - it "is not able to update the profile without display name as an admin" do fill_in "Display name", with: "" click_on "Update Profile" expect(page).to have_text("Display name can't be blank") end - it_should_behave_like "shows error for invalid phone numbers" + context "shows error for invalid phone number" do + it "shows error message for phone number < 12 digits" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + + fill_in "Phone number", with: "+141632489" + click_on("Update Profile") + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for phone number > 12 digits" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + + fill_in "Phone number", with: "+141632180923" + click_on("Update Profile") + + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for bad phone number" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + + fill_in("Phone number", with: "+141632u809o") + click_on("Update Profile") + + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for phone number without country code" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + + fill_in("Phone number", with: "+24163218092") + click_on("Update Profile") + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + end + + it "is able to send a confirmation email when Casa Admin updates their email" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path - it "is able to send a confrimation email when Casa Admin updates their email" do click_on "Change Email" expect(page).to have_field("New Email", disabled: false) @@ -257,6 +419,12 @@ end it "displays email errors messages when user is unable to set a email with incorrect current password" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + click_on "Change Email" fill_in "current_password_email", with: "12345" @@ -268,6 +436,12 @@ end it "displays password errors messages when admin is unable to set a password" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -281,6 +455,12 @@ end it "display success message when admin update password" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -293,12 +473,29 @@ end it "displays sms notification events for the casa admin user" do + org = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: org) + + SmsNotificationEvent.delete_all + SmsNotificationEvent.new(name: "sms_event_test_volunteer", user_type: Volunteer).save + SmsNotificationEvent.new(name: "sms_event_test_supervisor", user_type: Supervisor).save + SmsNotificationEvent.new(name: "sms_event_test_casa_admin", user_type: CasaAdmin).save + + sign_in admin + visit edit_users_path + expect(page).not_to have_content "sms_event_test_volunteer" expect(page).not_to have_content "sms_event_test_supervisor" expect(page).to have_content "sms_event_test_casa_admin" end it "notifies password changed by email", :aggregate_failures do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + click_on "Change Password" fill_in "current_password", with: "12345678" @@ -316,6 +513,12 @@ end it "displays admin error message if no communication preference is selected" do + org = create(:casa_org) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + uncheck "user_receive_email_notifications" click_on "Save Preferences" expect(page).to have_content "1 error prohibited this Casa admin from being saved:" @@ -323,6 +526,12 @@ end it "displays admin error message if SMS communication preference is selected without adding a valid phone number" do + org = create(:casa_org, twilio_enabled: true) + admin = create(:casa_admin, casa_org: org) + + sign_in admin + visit edit_users_path + uncheck "user_receive_email_notifications" check "user_receive_sms_notifications" click_on "Save Preferences" diff --git a/spec/system/volunteers/edit_spec.rb b/spec/system/volunteers/edit_spec.rb index 159231f93f..1b5588857c 100644 --- a/spec/system/volunteers/edit_spec.rb +++ b/spec/system/volunteers/edit_spec.rb @@ -1,97 +1,174 @@ require "rails_helper" RSpec.describe "volunteers/edit", type: :system do - let(:organization) { create(:casa_org) } - let(:admin) { create(:casa_admin, casa_org_id: organization.id) } - let(:volunteer) { create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) } - - it "shows invite and login info" do - sign_in admin - visit edit_volunteer_path(volunteer) - expect(page).to have_text "Added to system " - expect(page).to have_text "Invitation email sent never" - expect(page).to have_text "Last logged in" - expect(page).to have_text "Invitation accepted never" - expect(page).to have_text "Password reset last sent never" - expect(page).to have_text "Learning Hours This Year 0h 0min" - end - describe "updating volunteer personal data" do - before do - sign_in admin - visit edit_volunteer_path(volunteer) - fill_in "volunteer_display_name", with: "Kamisato Ayato" - end - context "with valid data" do it "updates successfully" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_display_name", with: "Kamisato Ayato" click_on "Submit" + expect(page).to have_text "Volunteer was successfully updated." end end context "with invalid data" do - let(:role) { "volunteer" } + context "shows error for invalid phone number" do + it "shows error message for phone number < 12 digits" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_phone_number", with: "+141632489" + click_on "Submit" + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for phone number > 12 digits" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_phone_number", with: "+141632180923" + click_on "Submit" + + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for bad phone number" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) - it_should_behave_like "shows error for invalid phone numbers" + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_phone_number", with: "+141632u809o" + click_on "Submit" + + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + + it "shows error message for phone number without country code" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_phone_number", with: "+24163218092" + click_on "Submit" + + expect(page).to have_text "Phone number must be 10 digits or 12 digits including country code (+1)" + end + end it "shows error message for duplicate email" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) volunteer.supervisor = build(:supervisor) + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "volunteer_display_name", with: "Kamisato Ayato" fill_in "volunteer_email", with: admin.email fill_in "volunteer_display_name", with: "Mickey Mouse" click_on "Submit" + expect(page).to have_text "already been taken" end it "shows error message for empty fields" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) volunteer.supervisor = create(:supervisor) + + sign_in admin + visit edit_volunteer_path(volunteer) + fill_in "volunteer_email", with: "" fill_in "volunteer_display_name", with: "" click_on "Submit" + expect(page).to have_text "can't be blank" end end end describe "updating a volunteer's email" do - before do - sign_in admin - visit edit_volunteer_path(volunteer) - @old_email = volunteer.email - fill_in "Email", with: "newemail@example.com" - click_on "Submit" - volunteer.reload - end - context "with a valid email" do it "sends volunteer a confirmation email and does not change the displayed email" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization) + old_email = volunteer.email + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "Email", with: "newemail@example.com" + click_on "Submit" + volunteer.reload + expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message) expect(ActionMailer::Base.deliveries.first.body.encoded) .to match("You can confirm your account email through the link below:") expect(page).to have_text "Volunteer was successfully updated. Confirmation Email Sent." - expect(page).to have_field("Email", with: @old_email) + expect(page).to have_field("Email", with: old_email) expect(volunteer.unconfirmed_email).to eq("newemail@example.com") end it "succesfully displays the new email once the user confirms" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization) + old_email = volunteer.email + + sign_in admin + visit edit_volunteer_path(volunteer) + + fill_in "Email", with: "newemail@example.com" + click_on "Submit" volunteer.reload volunteer.confirm visit edit_volunteer_path(volunteer) + expect(page).to have_field("Email", with: "newemail@example.com") - expect(page).to_not have_field("Email", with: @old_email) - expect(volunteer.old_emails).to eq([@old_email]) + expect(page).to_not have_field("Email", with: old_email) + expect(volunteer.old_emails).to eq([old_email]) end end end it "saves the user as inactive, but only if the admin confirms", js: true do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization) + sign_in admin visit edit_volunteer_path(volunteer) dismiss_confirm do + scroll_to(".actions") click_on "Deactivate volunteer" end expect(page).not_to have_text("Volunteer was deactivated on") @@ -105,7 +182,9 @@ end it "allows an admin to reactivate a volunteer" do - inactive_volunteer = build(:volunteer, casa_org_id: organization.id) + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + inactive_volunteer = build(:volunteer, casa_org: organization) inactive_volunteer.deactivate sign_in admin @@ -120,11 +199,12 @@ end it "allows the admin to unassign a volunteer from a supervisor" do + organization = create(:casa_org) supervisor = build(:supervisor, display_name: "Haka Haka", casa_org: organization) volunteer = create(:volunteer, display_name: "Bolu Bolu", supervisor: supervisor, casa_org: organization) + admin = create(:casa_admin, casa_org: organization) sign_in admin - visit edit_volunteer_path(volunteer) expect(page).to have_content("Current Supervisor: Haka Haka") @@ -135,13 +215,15 @@ end it "shows the admin the option to assign an unassigned volunteer to a different active supervisor" do + organization = create(:casa_org) volunteer = create(:volunteer, casa_org: organization) deactivated_supervisor = create(:supervisor, active: false, casa_org: organization, display_name: "Inactive Supervisor") active_supervisor = create(:supervisor, active: true, casa_org: organization, display_name: "Active Supervisor") + admin = create(:casa_admin, casa_org: organization) sign_in admin - visit edit_volunteer_path(volunteer) + expect(page).not_to have_select("supervisor_volunteer[supervisor_id]", with_options: [deactivated_supervisor.display_name]) expect(page).to have_select("supervisor_volunteer[supervisor_id]", options: [active_supervisor.display_name]) expect(page).to have_content("Select a Supervisor") @@ -149,27 +231,36 @@ end context "when the volunteer is unassigned from all of their cases" do - let!(:casa_case_1) { create(:casa_case, casa_org: organization, case_number: "CINA1") } - let!(:casa_case_2) { create(:casa_case, casa_org: organization, case_number: "CINA2") } - let!(:case_assignment_1) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1) } - let!(:case_assignment_2) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2) } - - before do - case_assignment_1.active = false - case_assignment_2.active = false - case_assignment_1.save - case_assignment_2.save - end - it "does not show any active assignment status in the Manage Cases section" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + casa_case_1 = create(:casa_case, casa_org: organization, case_number: "CINA1") + casa_case_2 = create(:casa_case, casa_org: organization, case_number: "CINA2") + create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1) + create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2) + casa_case_1.update!(active: false) + casa_case_2.update!(active: false) + sign_in admin visit edit_volunteer_path(volunteer) + within "#manage_cases" do expect(page).not_to have_content("Volunteer is Active") end end it "shows the unassigned cases in the Manage Cases section" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + casa_case_1 = create(:casa_case, casa_org: organization, case_number: "CINA1") + casa_case_2 = create(:casa_case, casa_org: organization, case_number: "CINA2") + case_assignment_1 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1) + case_assignment_2 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2) + casa_case_1.update!(active: false) + casa_case_2.update!(active: false) + sign_in admin visit edit_volunteer_path(volunteer) @@ -183,6 +274,19 @@ end it "shows assignment status as 'Volunteer is Unassigned' for each unassigned case" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + casa_case_1 = create(:casa_case, casa_org: organization, case_number: "CINA1") + casa_case_2 = create(:casa_case, casa_org: organization, case_number: "CINA2") + case_assignment_1 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1) + case_assignment_2 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2) + + case_assignment_1.active = false + case_assignment_2.active = false + case_assignment_1.save + case_assignment_2.save + sign_in admin visit edit_volunteer_path(volunteer) @@ -198,24 +302,28 @@ context "with a deactivated case" do it "displays inactive message" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) deactivated_casa_case = create(:casa_case, active: false, casa_org: volunteer.casa_org, volunteers: [volunteer]) - sign_in admin + sign_in admin visit edit_volunteer_path(volunteer) + expect(page).to have_text "Case was deactivated on: #{I18n.l(deactivated_casa_case.updated_at, format: :standard, default: nil)}" end end context "when volunteer is assigned to multiple cases" do - let(:casa_org) { build(:casa_org) } - let!(:supervisor) { create(:casa_admin, casa_org: casa_org) } - let!(:volunteer) { create(:volunteer, casa_org: casa_org, display_name: "AAA") } - let!(:casa_case_1) { create(:casa_case, casa_org: casa_org, case_number: "CINA1") } - let!(:casa_case_2) { create(:casa_case, casa_org: casa_org, case_number: "CINA2") } - it "supervisor assigns multiple cases to the same volunteer" do + casa_org = build(:casa_org) + supervisor = create(:casa_admin, casa_org: casa_org) + volunteer = create(:volunteer, casa_org: casa_org, display_name: "AAA") + casa_case_1 = create(:casa_case, casa_org: casa_org, case_number: "CINA1") + casa_case_2 = create(:casa_case, casa_org: casa_org, case_number: "CINA2") + sign_in supervisor - visit edit_volunteer_path(volunteer.id) + visit edit_volunteer_path(volunteer) select casa_case_1.case_number, from: "Select a Case" click_on "Assign Case" @@ -230,18 +338,16 @@ end context "with previously assigned cases" do - let(:casa_org) { build(:casa_org) } - let!(:supervisor) { create(:casa_admin, casa_org: casa_org) } - let!(:volunteer) { create(:volunteer, casa_org: casa_org, display_name: "AAA") } - let!(:casa_case_1) { build(:casa_case, casa_org: casa_org, case_number: "CINA1") } - let!(:casa_case_2) { build(:casa_case, casa_org: casa_org, case_number: "CINA2") } - it "shows the unassign button for assigned cases and not for unassigned cases" do - sign_in supervisor - + casa_org = build(:casa_org) + supervisor = create(:casa_admin, casa_org: casa_org) + volunteer = create(:volunteer, casa_org: casa_org, display_name: "AAA") + casa_case_1 = build(:casa_case, casa_org: casa_org, case_number: "CINA1") + casa_case_2 = build(:casa_case, casa_org: casa_org, case_number: "CINA2") assignment1 = volunteer.case_assignments.create(casa_case: casa_case_1, active: true) assignment2 = volunteer.case_assignments.create(casa_case: casa_case_2, active: false) + sign_in supervisor visit edit_volunteer_path(volunteer) within("#case_assignment_#{assignment1.id}") do @@ -265,12 +371,13 @@ end describe "inactive case visibility" do - let!(:active_casa_case) { create(:casa_case, casa_org: organization, case_number: "ACTIVE") } - let!(:inactive_casa_case) { create(:casa_case, casa_org: organization, active: false, case_number: "INACTIVE") } - let!(:volunteer) { create(:volunteer, display_name: "Awesome Volunteer", casa_org: organization) } - let(:supervisor) { build(:casa_admin, casa_org: organization) } - it "supervisor does not have inactive cases as an option to assign to a volunteer" do + organization = build(:casa_org) + active_casa_case = create(:casa_case, casa_org: organization, case_number: "ACTIVE") + inactive_casa_case = create(:casa_case, casa_org: organization, active: false, case_number: "INACTIVE") + volunteer = create(:volunteer, display_name: "Awesome Volunteer", casa_org: organization) + supervisor = build(:casa_admin, casa_org: organization) + sign_in supervisor visit edit_volunteer_path(volunteer) @@ -280,11 +387,12 @@ end describe "resend invite" do - let(:supervisor) { create(:supervisor, casa_org: organization) } + it "allows a supervisor resend invitation to a volunteer" do + organization = create(:casa_org) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + supervisor = create(:supervisor, casa_org: organization) - it "allows a supervisor resend invitation to a volunteer", js: true do sign_in supervisor - visit edit_volunteer_path(volunteer) click_on "Resend Invitation" @@ -297,9 +405,12 @@ end end - it "allows an administrator resend invitation to a volunteer", js: true do - sign_in admin + it "allows an administrator resend invitation to a volunteer" do + organization = create(:casa_org) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + admin = create(:casa_admin, casa_org: organization) + sign_in admin visit edit_volunteer_path(volunteer) click_on "Resend Invitation" @@ -312,20 +423,25 @@ end describe "Send Reactivation (SMS)" do - it "allows admin to send a reactivation SMS to a volunteer if their org has twilio enabled", js: true do + it "allows admin to send a reactivation SMS to a volunteer if their org has twilio enabled" do + organization = create(:casa_org, twilio_enabled: true) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + admin = create(:casa_admin, casa_org: organization) + sign_in admin visit edit_volunteer_path(volunteer) + expect(page).to have_content("Send Reactivation Alert (SMS)") expect(page).not_to have_content("Enable Twilio") expect(page).to have_selector("#twilio_enabled") end context " admin's organization does not have twilio enabled" do - let(:org_twilio) { create(:casa_org, twilio_enabled: false) } - let(:admin_twilio) { create(:casa_admin, casa_org_id: org_twilio.id) } - let(:volunteer_twilio) { create(:volunteer, casa_org_id: org_twilio.id) } + it "displays a disabed (SMS) button with appropriate message" do + org_twilio = create(:casa_org, twilio_enabled: false) + admin_twilio = create(:casa_admin, casa_org: org_twilio) + volunteer_twilio = create(:volunteer, casa_org: org_twilio) - it "displays a disabed (SMS) button with appropriate message", js: true do sign_in admin_twilio visit edit_volunteer_path(volunteer_twilio) @@ -335,14 +451,13 @@ end end - describe "send reminder as a supervisor", js: true do - let(:supervisor) { create(:supervisor, casa_org: organization) } + describe "send reminder as a supervisor" do + it "emails the volunteer" do + organization = create(:casa_org) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + supervisor = create(:supervisor, casa_org: organization) - before(:each) do sign_in supervisor - end - - it "emails the volunteer" do visit edit_volunteer_path(volunteer) expect(page).to have_button("Send Reminder") @@ -355,6 +470,11 @@ end it "emails volunteer and cc's the supervisor" do + organization = create(:casa_org) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + supervisor = create(:supervisor, casa_org: organization) + + sign_in supervisor visit edit_volunteer_path(volunteer) check "with_cc" @@ -365,7 +485,11 @@ end it "emails the volunteer without a supervisor" do + organization = create(:casa_org) volunteer_without_supervisor = create(:volunteer) + supervisor = create(:supervisor, casa_org: organization) + + sign_in supervisor visit edit_volunteer_path(volunteer_without_supervisor) check "with_cc" @@ -377,12 +501,14 @@ end describe "send reminder as admin" do - before(:each) do + it "emails the volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + sign_in admin visit edit_volunteer_path(volunteer) - end - it "emails the volunteer" do expect(page).to have_button("Send Reminder") expect(page).to have_text("Send CC to Supervisor and Admin") @@ -392,6 +518,12 @@ end it "emails the volunteer and cc's their supervisor and admin" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) check "with_cc" click_on "Send Reminder" @@ -402,26 +534,19 @@ end describe "impersonate button" do - let(:volunteer) { create(:volunteer, casa_org: organization, display_name: "John Doe") } - - before do - sign_in user - visit edit_volunteer_path(volunteer) - end - context "when user is an admin" do - let(:user) { create(:casa_admin, casa_org: organization) } - - it "shows the impersonate button" do - expect(page).to have_link("Impersonate") - end - it "impersonates the volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org: organization) + volunteer = create(:volunteer, casa_org: organization, display_name: "John Doe") + sign_in admin + visit edit_volunteer_path(volunteer) + click_on "Impersonate" within(".sidebar-nav-wrapper") do expect(page).to have_text( - "You (#{user.display_name}) are signed in as John Doe. " \ + "You (#{admin.display_name}) are signed in as John Doe. " \ "Click here to stop impersonating." ) end @@ -429,18 +554,18 @@ end context "when user is a supervisor" do - let(:user) { create(:supervisor, casa_org: organization) } - - it "shows the impersonate button" do - expect(page).to have_link("Impersonate") - end - it "impersonates the volunteer" do + organization = create(:casa_org) + supervisor = create(:supervisor, casa_org: organization) + volunteer = create(:volunteer, casa_org: organization, display_name: "John Doe") + sign_in supervisor + visit edit_volunteer_path(volunteer) + click_on "Impersonate" within(".sidebar-nav-wrapper") do expect(page).to have_text( - "You (#{user.display_name}) are signed in as John Doe. " \ + "You (#{supervisor.display_name}) are signed in as John Doe. " \ "Click here to stop impersonating." ) end @@ -448,9 +573,14 @@ end context "when user is a volunteer" do - let(:user) { create(:volunteer, casa_org: organization) } - it "does not show the impersonate button", :aggregate_failures do + organization = create(:casa_org) + volunteer = create(:volunteer, casa_org: organization) + user = create(:volunteer, casa_org: organization) + + sign_in user + visit edit_volunteer_path(volunteer) + expect(page).not_to have_link("Impersonate") expect(current_path).not_to eq(edit_volunteer_path(volunteer)) end @@ -458,16 +588,17 @@ end context "logged in as an admin" do - let!(:note_1) { volunteer.notes.create(creator: admin, content: "Note_1") } - let!(:note_2) { volunteer.notes.create(creator: admin, content: "Note_2") } - let!(:note_3) { volunteer.notes.create(creator: admin, content: "Note_3") } + it "can save notes about a volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + volunteer.notes.create(creator: admin, content: "Note_1") + volunteer.notes.create(creator: admin, content: "Note_2") + volunteer.notes.create(creator: admin, content: "Note_3") - before do sign_in admin visit edit_volunteer_path(volunteer) - end - it "can save notes about a volunteer" do freeze_time do current_date = Date.today fill_in("note[content]", with: "Great job today.") @@ -485,6 +616,16 @@ end it "can delete notes about a volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + volunteer.notes.create(creator: admin, content: "Note_1") + volunteer.notes.create(creator: admin, content: "Note_2") + volunteer.notes.create(creator: admin, content: "Note_3") + + sign_in admin + visit edit_volunteer_path(volunteer) + expect(page).to have_css ".notes .table tbody tr", count: 3 click_on("Delete", match: :first) @@ -494,17 +635,18 @@ end context "logged in as a supervisor" do - let!(:note_1) { volunteer.notes.create(creator: admin, content: "Note_1") } - let!(:note_2) { volunteer.notes.create(creator: admin, content: "Note_2") } - let!(:note_3) { volunteer.notes.create(creator: admin, content: "Note_3") } + it "can save notes about a volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + supervisor = volunteer.supervisor + volunteer.notes.create(creator: admin, content: "Note_1") + volunteer.notes.create(creator: admin, content: "Note_2") + volunteer.notes.create(creator: admin, content: "Note_3") - before do - volunteer.supervisor = create(:supervisor) - sign_in volunteer.supervisor + sign_in supervisor visit edit_volunteer_path(volunteer) - end - it "can save notes about a volunteer" do freeze_time do current_date = Date.today fill_in("note[content]", with: "Great job today.") @@ -522,6 +664,17 @@ end it "can delete notes about a volunteer" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + supervisor = volunteer.supervisor + volunteer.notes.create(creator: admin, content: "Note_1") + volunteer.notes.create(creator: admin, content: "Note_2") + volunteer.notes.create(creator: admin, content: "Note_3") + + sign_in supervisor + visit edit_volunteer_path(volunteer) + expect(page).to have_css ".notes .table tbody tr", count: 3 click_on("Delete", match: :first) @@ -531,30 +684,39 @@ end context "logged in as volunteer" do - before do + it "can't see the notes section" do + organization = create(:casa_org) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization) sign_in volunteer visit edit_volunteer_path(volunteer) - end - it "can't see the notes section" do expect(page).not_to have_selector(".notes") expect(page).to have_content("Sorry, you are not authorized to perform this action.") end end describe "updating volunteer address" do - before do - sign_in admin - visit edit_volunteer_path(volunteer) - end - context "with mileage reimbursement turned on" do it "shows 'Mailing address' label" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + expect(page).to have_text "Mailing address" expect(page).to have_selector "input[type=text][id=volunteer_address_attributes_content]" end it "updates successfully" do + organization = create(:casa_org) + admin = create(:casa_admin, casa_org_id: organization.id) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + fill_in "volunteer_address_attributes_content", with: "123 Main St" click_on "Submit" expect(page).to have_text "Volunteer was successfully updated." @@ -563,9 +725,14 @@ end context "with mileage reimbursement turned off" do - let(:organization) { create(:casa_org, show_driving_reimbursement: false) } - let(:volunteer) { create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) } it "won't show 'Mailing address' label" do + organization = create(:casa_org, show_driving_reimbursement: false) + volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization) + admin = create(:casa_admin, casa_org_id: organization.id) + + sign_in admin + visit edit_volunteer_path(volunteer) + expect(page).not_to have_text "Mailing address" expect(page).not_to have_selector "input[type=text][id=volunteer_address_attributes_content]" end diff --git a/spec/system/volunteers/index_spec.rb b/spec/system/volunteers/index_spec.rb index 2950b45493..b37c72464b 100644 --- a/spec/system/volunteers/index_spec.rb +++ b/spec/system/volunteers/index_spec.rb @@ -89,8 +89,6 @@ visit volunteers_path expect(page).to have_selector(".volunteer-filters") - # by default, only active users are shown - expect(page.all("table#volunteers tbody tr").count).to eq(assigned_volunteers.count + unassigned_volunteers.count) assigned_volunteers.each do |assigned_volunteer| expect(page).to have_text assigned_volunteer.display_name end @@ -180,7 +178,7 @@ end context "when timed out" do - it "prompts login", js: true do + it "prompts login" do sign_in admin visit volunteers_path click_on "Supervisor" @@ -250,6 +248,61 @@ end end + it "can persist 'show/hide' column preference settings", js: true do + sign_in supervisor + + visit volunteers_path + + expect(page).to have_text("Pick displayed columns") + within("#volunteers") do + expect(page).to have_text("Name") + expect(page).to have_text("Email") + expect(page).to have_text("Status") + expect(page).to have_text("Assigned To Transition Aged Youth") + expect(page).to have_text("Case Number(s)") + expect(page).to have_text("Last Attempted Contact") + expect(page).to have_text("Contacts Made in Past 60 Day") + end + + click_button "Pick displayed columns" + + uncheck "Name" + uncheck "Status" + uncheck "Contact Made In Past 60 Days" + uncheck "Last Attempted Contact" + + within(".modal-dialog") do + click_button "Close" + end + + within("#volunteers") do + expect(page).to have_no_text("Name") + expect(page).to have_no_text("Status") + expect(page).to have_no_text("Contact Made In Past 60 Days") + expect(page).to have_no_text("Last Attempted Contact") + expect(page).to have_text("Email") + expect(page).to have_text("Assigned To Transition Aged Youth") + expect(page).to have_text("Case Number(s)") + end + + sign_out supervisor + visit volunteers_path + + sign_in supervisor + visit volunteers_path + + # Expectations after page reload + within("#volunteers") do + expect(page).to have_no_text("Name") + expect(page).to have_no_text("Status") + expect(page).to have_no_text("Contact Made In Past 60 Days") + expect(page).to have_no_text("Last Attempted Contact") + expect(page).to have_text("Email") + expect(page).to have_text("Assigned To Transition Aged Youth") + expect(page).to have_text("Case Number(s)") + end + end + context "with volunteers" do let(:supervisor) { create(:supervisor, :with_volunteers) } @@ -267,7 +320,7 @@ end context "when timed out" do - it "prompts login", js: true do + it "prompts login" do sign_in supervisor visit volunteers_path click_on "Supervisor" diff --git a/spec/views/casa_cases/index.html.erb_spec.rb b/spec/views/casa_cases/index.html.erb_spec.rb new file mode 100644 index 0000000000..a11abb9738 --- /dev/null +++ b/spec/views/casa_cases/index.html.erb_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe "casa_cases/index", type: :view do + context "when accessed by an admin" do + it "can see the New Case button" do + organization = create(:casa_org) + user = build_stubbed(:casa_admin, casa_org: organization) + enable_pundit(view, user) + allow(view).to receive(:current_user).and_return(user) + + assign :casa_cases, [] + assign :duties, OtherDuty.none + + render template: "casa_cases/index" + + expect(rendered).to have_link "New Case" + end + end + + context "when accessed by a supervisor" do + it "does not see the New Case button" do + organization = create(:casa_org) + user = build_stubbed(:supervisor, casa_org: organization) + enable_pundit(view, user) + allow(view).to receive(:current_user).and_return(user) + + assign :casa_cases, [] + assign :duties, OtherDuty.none + + render template: "casa_cases/index" + + expect(rendered).to_not have_link "New Case" + end + end +end diff --git a/spec/views/casa_cases/new.html.erb_spec.rb b/spec/views/casa_cases/new.html.erb_spec.rb index 0cd642ce08..2e398d74ca 100644 --- a/spec/views/casa_cases/new.html.erb_spec.rb +++ b/spec/views/casa_cases/new.html.erb_spec.rb @@ -1,24 +1,36 @@ require "rails_helper" RSpec.describe "casa_cases/new", type: :view do - subject { render template: "casa_cases/new" } + context "while signed in as admin" do + it "has youth birth month and year" do + user = build_stubbed(:casa_admin) + enable_pundit(view, user) + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:current_organization).and_return(user.casa_org) + + assign :casa_case, build(:casa_case, casa_org: user.casa_org) + assign :contact_types, [] - before(:each) do - assign :casa_case, CasaCase.new(casa_org: user.casa_org) - assign :contact_types, [] + render template: "casa_cases/new" - enable_pundit(view, user) - allow(view).to receive(:current_user).and_return(user) - allow(view).to receive(:current_organization).and_return(user.casa_org) + expect(rendered).to include CGI.escapeHTML("Youth's Birth Month & Year") + end end - context "while signed in as admin" do - let(:user) { build_stubbed(:casa_admin) } + context "when trying to assign a volunteer to a case" do + it "should not be able to assign volunteers" do + user = build_stubbed(:casa_admin) + enable_pundit(view, user) + allow(view).to receive(:current_user).and_return(user) + allow(view).to receive(:current_organization).and_return(user.casa_org) - before do - sign_in user - end + assign :casa_case, build(:casa_case, casa_org: user.casa_org) + assign :contact_types, [] + + render template: "casa_cases/new" - it { is_expected.to include(CGI.escapeHTML("Youth's Birth Month & Year")) } + expect(rendered).not_to have_content("Manage Volunteers") + expect(rendered).not_to have_css("#volunteer-assignment") + end end end diff --git a/spec/views/casa_orgs/edit.html.erb_spec.rb b/spec/views/casa_orgs/edit.html.erb_spec.rb new file mode 100644 index 0000000000..e08833db09 --- /dev/null +++ b/spec/views/casa_orgs/edit.html.erb_spec.rb @@ -0,0 +1,151 @@ +require "rails_helper" + +RSpec.describe "casa_org/edit", type: :view do + before do + assign(:contact_type_groups, []) + assign(:contact_types, []) + assign(:hearing_types, []) + assign(:judges, []) + assign(:learning_hour_types, []) + assign(:sent_emails, []) + + sign_in build_stubbed(:casa_admin) + end + + it "has casa org edit page text" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + + render template: "casa_org/edit" + + expect(rendered).to have_text "Editing CASA Organization" + expect(rendered).to_not have_text "sign in before continuing" + expect(rendered).to have_selector("input[required=required]", id: "casa_org_name") + end + + it "has contact types content" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + contact_type = build_stubbed(:contact_type, name: "Contact type 1") + assign(:contact_types, [contact_type]) + + render template: "casa_org/edit" + + expect(rendered).to have_text("Contact type 1") + expect(rendered).to have_text(contact_type.name) + expect(rendered).to have_table("contact-types", + with_rows: + [ + ["Contact type 1", "Yes", "Edit"] + ]) + end + + it "has contact type groups content" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + contact_type_group = build_stubbed(:contact_type_group, casa_org: organization, name: "Contact type group 1") + assign(:contact_type_groups, [contact_type_group]) + + render template: "casa_org/edit" + + expect(rendered).to have_text("Contact type group 1") + expect(rendered).to have_text(contact_type_group.name) + expect(rendered).to have_table("contact-type-groups", + with_rows: [ + ["Contact type group 1", "Yes", "Edit"] + ]) + end + + it "has hearing types content" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + hearing_type = build_stubbed(:hearing_type, casa_org: organization, name: "Hearing type 1") + assign(:hearing_types, [hearing_type]) + + render template: "casa_org/edit" + + expect(rendered).to have_text("Hearing type 1") + expect(rendered).to have_table("hearing-types", + with_rows: + [ + ["Hearing type 1", "Yes", "Edit"] + ]) + end + + it "has judge content" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + judge = build_stubbed(:judge, casa_org: organization, name: "Joey Tom") + assign(:judges, [judge]) + + render template: "casa_org/edit" + + expect(rendered).to have_text(judge.name) + end + + it "does not show download prompt with no custom template" do + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + + render template: "casa_org/edit" + + expect(rendered).not_to have_text("Download Current Template") + end + + it "has sent emails content" do + organization = build_stubbed(:casa_org) + admin = build_stubbed(:casa_admin, casa_org: organization) + allow(view).to receive(:current_organization).and_return(organization) + + sent_email = build_stubbed(:sent_email, user: admin, created_at: Time.zone.local(2021, 1, 2, 12, 30, 0)) + assign(:sent_emails, [sent_email]) + + render template: "casa_org/edit" + + expect(rendered).to have_text(sent_email.sent_address) + expect(rendered).to have_table("sent-emails", + with_rows: [ + ["Mailer Type", "Mail Action Category", admin.email, "12:30pm 02 Jan 2021"] + ]) + end + + context "with a template uploaded" do + it "renders a prompt to download current template" do + organization = create(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + + organization.court_report_template.attach(io: File.open("#{Rails.root}/app/documents/templates/default_report_template.docx"), filename: 'default_report_template +.docx', content_type: "application/docx") + + render template: "casa_org/edit" + + expect(rendered).to have_text("Download Current Template") + end + end + + describe "additional expense feature flag" do + context "enabled" do + it "has option to enable additional expenses" do + FeatureFlagService.enable!(FeatureFlagService::SHOW_ADDITIONAL_EXPENSES_FLAG) + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + + render template: "casa_org/edit" + + expect(rendered).to have_text("Volunteers can add Other Expenses") + end + end + + context "disabled" do + it "has option to enable additional expenses" do + FeatureFlagService.disable!(FeatureFlagService::SHOW_ADDITIONAL_EXPENSES_FLAG) + organization = build_stubbed(:casa_org) + allow(view).to receive(:current_organization).and_return(organization) + + render template: "casa_org/edit" + + expect(rendered).not_to have_text("Volunteers can add Other Expenses") + end + end + end +end diff --git a/spec/views/court_dates/new.html.erb_spec.rb b/spec/views/court_dates/new.html.erb_spec.rb index 986ce96ae4..e062666af7 100644 --- a/spec/views/court_dates/new.html.erb_spec.rb +++ b/spec/views/court_dates/new.html.erb_spec.rb @@ -13,7 +13,7 @@ let(:user) { build_stubbed(:casa_admin) } let(:casa_case) { create(:casa_case) } - it { is_expected.to have_selector("h2", text: "New Court Date") } + it { is_expected.to have_selector("h1", text: "New Court Date") } it { is_expected.to have_selector("h6", text: casa_case.case_number) } it { is_expected.to have_link(casa_case.case_number, href: "/casa_cases/#{casa_case.case_number.parameterize}") } it { is_expected.to have_selector(".primary-btn") } diff --git a/spec/views/layouts/header.html.erb_spec.rb b/spec/views/layouts/header.html.erb_spec.rb index 9a15852c04..465bb7a9b4 100644 --- a/spec/views/layouts/header.html.erb_spec.rb +++ b/spec/views/layouts/header.html.erb_spec.rb @@ -12,8 +12,10 @@ def true_user enable_pundit(view, user) allow(view).to receive(:true_user).and_return(user) allow(view).to receive(:current_user).and_return(user) - allow(view).to receive(:current_organization).and_return(1) allow(view).to receive(:current_role).and_return(user.role) + + casa_org = build_stubbed :casa_org + allow(view).to receive(:current_organization).and_return(casa_org) end context "when logged in as a casa admin" do diff --git a/spec/views/layouts/sidebar.html.erb_spec.rb b/spec/views/layouts/sidebar.html.erb_spec.rb index 9f07b8e9c0..30cdb2e5a0 100644 --- a/spec/views/layouts/sidebar.html.erb_spec.rb +++ b/spec/views/layouts/sidebar.html.erb_spec.rb @@ -71,7 +71,7 @@ def true_user render partial: "layouts/sidebar" expect(rendered).to have_link("My Cases", href: "/casa_cases") - expect(rendered).to have_link("Case Contacts", href: "/case_contacts") + expect(rendered).to have_link("All", href: "/case_contacts") expect(rendered).to have_link("Generate Court Report", href: "/case_court_reports") expect(rendered).to_not have_link("Export Data", href: "/reports") expect(rendered).to_not have_link("Volunteers", href: "/volunteers") diff --git a/spec/views/notifications/index.html.erb_spec.rb b/spec/views/notifications/index.html.erb_spec.rb index 3724bb90bf..592ac8c1aa 100644 --- a/spec/views/notifications/index.html.erb_spec.rb +++ b/spec/views/notifications/index.html.erb_spec.rb @@ -40,8 +40,8 @@ let(:patch_note_group_no_volunteers) { create(:patch_note_group, :only_supervisors_and_admins) } let(:patch_note_type_a) { create(:patch_note_type, name: "patch_note_type_a") } let(:patch_note_type_b) { create(:patch_note_type, name: "patch_note_type_b") } - let(:patch_note_1) { create(:patch_note, note: "*Sy@\\(\\\"Q7", patch_note_type: patch_note_type_a) } - let(:patch_note_2) { create(:patch_note, note: "(W!;Ros>cIWNKX}", patch_note_type: patch_note_type_b) } + let(:patch_note_1) { create(:patch_note, note: "Patch Note 1", patch_note_type: patch_note_type_a) } + let(:patch_note_2) { create(:patch_note, note: "Patch Note B", patch_note_type: patch_note_type_b) } before do Health.instance.update_attribute(:latest_deploy_time, Date.today) @@ -68,28 +68,14 @@ queryable_html = Nokogiri.HTML5(rendered) - patch_note_notification_information_container = queryable_html.css("#patch-note-notification div.my-1").first - patch_note_type_a_header = queryable_html.xpath("//*[text()[contains(.,'#{patch_note_type_a.name}')]]").first - patch_note_type_a_header_index = patch_note_notification_information_container.children.index(patch_note_type_a_header) patch_note_type_b_header = queryable_html.xpath("//*[text()[contains(.,'#{patch_note_type_b.name}')]]").first - patch_note_type_b_header_index = patch_note_notification_information_container.children.index(patch_note_type_b_header) - - patch_note_1_list_item = queryable_html.xpath("//*[text()[contains(.,'#{patch_note_1.note}')]]").first - patch_note_1_unordered_list_index = patch_note_notification_information_container.children.index(patch_note_1_list_item.parent) - patch_note_2_list_item = queryable_html.xpath("//*[text()[contains(.,'#{patch_note_2.note}')]]").first - patch_note_2_unordered_list_index = patch_note_notification_information_container.children.index(patch_note_2_list_item.parent) - - expect(patch_note_type_a_header_index).to be < patch_note_1_unordered_list_index - expect(patch_note_type_b_header_index).to be < patch_note_2_unordered_list_index - if patch_note_type_a_header_index < patch_note_type_b_header_index - expect(patch_note_type_b_header_index).to be > patch_note_1_unordered_list_index - end + patch_note_a_data = patch_note_type_a_header.parent.css("ul").first + expect(patch_note_a_data.text).to include(patch_note_1.note) - if patch_note_type_b_header_index < patch_note_type_a_header_index - expect(patch_note_type_a_header_index).to be > patch_note_2_unordered_list_index - end + patch_note_b_data = patch_note_type_b_header.parent.css("ul").first + expect(patch_note_b_data.text).to include(patch_note_2.note) end end end diff --git a/spec/views/supervisors/index.html.erb_spec.rb b/spec/views/supervisors/index.html.erb_spec.rb index 786f6561c3..8643fde6ce 100644 --- a/spec/views/supervisors/index.html.erb_spec.rb +++ b/spec/views/supervisors/index.html.erb_spec.rb @@ -1,53 +1,61 @@ require "rails_helper" RSpec.describe "supervisors/index", type: :view do - let(:user) {} - - before do - enable_pundit(view, user) - allow(view).to receive(:current_user).and_return(user) - assign :supervisors, [] - assign :available_volunteers, [] - sign_in user - end - context "when logged in as an admin" do - let(:user) { build_stubbed :casa_admin } - let!(:casa_cases) { create_list(:casa_case, 2, court_dates: []) } - it "can access the 'New Supervisor' button" do + user = create(:casa_admin) + enable_pundit(view, user) + casa_cases = create_list(:casa_case, 2, court_dates: []) assign :casa_cases, casa_cases + assign :supervisors, [] + assign :available_volunteers, [] + + sign_in user + render template: "supervisors/index" expect(rendered).to have_link("New Supervisor", href: new_supervisor_path) end it "show casa_cases list" do + user = create(:casa_admin) + enable_pundit(view, user) + casa_case1 = create(:casa_case, + case_number: "123", + active: true, + birth_month_year_youth: Date.new(1900)) + casa_case2 = create(:casa_case, + case_number: "456", + active: false, + birth_month_year_youth: Date.new(2100)) + assign :casa_cases, [casa_case1, casa_case2] assign :supervisors, [] - assign :casa_cases, casa_cases + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" - casa_cases.each do |casa_case| - expect(rendered).to have_text(casa_case.case_number) - expect(rendered).to have_text(casa_case.hearing_type_name) - expect(rendered).to have_text(casa_case.judge_name) - expect(rendered).to have_text(casa_case.decorate.status) - expect(rendered).to have_text(casa_case.decorate.transition_aged_youth) - end + expect(rendered).to have_text "123" + expect(rendered).to have_text "Active" + expect(rendered).to have_text "Yes #{CasaCase::TRANSITION_AGE_YOUTH_ICON}" + + expect(rendered).to have_text "456" + expect(rendered).to have_text "Inactive" + expect(rendered).to have_text "No #{CasaCase::NON_TRANSITION_AGE_YOUTH_ICON}" end context "when a supervisor has volunteers who have and have not submitted a case contact in 14 days" do - let(:supervisor) { create(:supervisor) } - let!(:volunteer_with_recently_created_contacts) { + it "shows positive and negative numbers" do + supervisor = create(:supervisor) + enable_pundit(view, supervisor) create(:volunteer, :with_cases_and_contacts, supervisor: supervisor) - } - let!(:volunteer_without_recently_created_contacts) { create(:volunteer, :with_casa_cases, supervisor: supervisor) - } - it "shows positive and negative numbers" do assign :supervisors, [supervisor] - assign :casa_cases, casa_cases + assign :casa_cases, [] + assign :available_volunteers, [] + + sign_in supervisor render template: "supervisors/index" parsed_html = Nokogiri.HTML5(rendered) @@ -57,9 +65,18 @@ end it "accurately displays the number of active and inactive volunteers per supervisor" do - create(:volunteer, :with_cases_and_contacts, supervisor: supervisor) + user = create(:casa_admin) + enable_pundit(view, user) + supervisor = create(:supervisor) + create_list(:volunteer, 2, :with_cases_and_contacts, supervisor: supervisor) + create(:volunteer, :with_casa_cases, supervisor: supervisor) + casa_cases = create_list(:casa_case, 2, court_dates: []) + assign :supervisors, [supervisor] assign :casa_cases, casa_cases + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" parsed_html = Nokogiri.HTML5(rendered) @@ -79,14 +96,18 @@ end context "when a supervisor only has volunteers who have not submitted a case contact in 14 days" do - let(:supervisor) { create(:supervisor) } - let!(:volunteer_without_recently_created_contacts) { + it "omits the attempted contact stat bar" do + user = create(:casa_admin) + enable_pundit(view, user) + supervisor = create(:supervisor) create(:volunteer, :with_casa_cases, supervisor: supervisor) - } + casa_cases = create_list(:casa_case, 2, court_dates: []) - it "omits the attempted contact stat bar" do assign :supervisors, [supervisor] assign :casa_cases, casa_cases + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" parsed_html = Nokogiri.HTML5(rendered) @@ -97,14 +118,18 @@ end context "when a supervisor only has volunteers who have submitted a case contact in 14 days" do - let(:supervisor) { create(:supervisor) } - let!(:volunteer_with_recently_created_contacts) { + it "shows the end of the attempted contact bar instead of the no attempted contact bar" do + user = create(:casa_admin) + enable_pundit(view, user) + supervisor = create(:supervisor) create(:volunteer, :with_cases_and_contacts, supervisor: supervisor) - } + casa_cases = create_list(:casa_case, 2, court_dates: []) - it "shows the end of the attempted contact bar instead of the no attempted contact bar" do assign :supervisors, [supervisor] assign :casa_cases, casa_cases + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" parsed_html = Nokogiri.HTML5(rendered) @@ -115,11 +140,17 @@ end context "when a supervisor does not have volunteers" do - let(:supervisor) { create(:supervisor) } - it "shows a no assigned volunteers message instead of attempted and no attempted contact bars" do + user = create(:casa_admin) + enable_pundit(view, user) + supervisor = create(:supervisor) + casa_cases = create_list(:casa_case, 2, court_dates: []) + assign :supervisors, [supervisor] assign :casa_cases, casa_cases + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" parsed_html = Nokogiri.HTML5(rendered) @@ -132,11 +163,16 @@ end context "when logged in as a supervisor" do - let(:user) { build_stubbed :supervisor } - let!(:casa_cases) { create_list(:casa_case, 2, court_dates: []) } - it "cannot access the 'New Supervisor' button" do + user = create(:supervisor) + enable_pundit(view, user) + casa_cases = create_list(:casa_case, 2, court_dates: []) + assign :casa_cases, casa_cases + assign :supervisors, [] + assign :available_volunteers, [] + + sign_in user render template: "supervisors/index" expect(rendered).to_not have_link("New Supervisor", href: new_supervisor_path) diff --git a/spec/views/volunteers/edit.html.erb_spec.rb b/spec/views/volunteers/edit.html.erb_spec.rb index b7a4c0a664..dc75043205 100644 --- a/spec/views/volunteers/edit.html.erb_spec.rb +++ b/spec/views/volunteers/edit.html.erb_spec.rb @@ -1,14 +1,14 @@ require "rails_helper" RSpec.describe "volunteers/edit", type: :view do - let(:org) { create :casa_org } - let(:volunteer) { create :volunteer, casa_org: org } - it "allows an administrator to edit a volunteers email address" do administrator = build_stubbed :casa_admin enable_pundit(view, administrator) + org = create :casa_org + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(administrator) allow(view).to receive(:current_organization).and_return(administrator.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -20,8 +20,11 @@ it "allows an administrator to edit a volunteers phone number" do administrator = build_stubbed :casa_admin enable_pundit(view, administrator) + org = create :casa_org + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(administrator) allow(view).to receive(:current_organization).and_return(administrator.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -33,8 +36,11 @@ it "allows a supervisor to edit a volunteers email address" do supervisor = build_stubbed :supervisor enable_pundit(view, supervisor) + org = create :casa_org + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(supervisor) allow(view).to receive(:current_organization).and_return(supervisor.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -44,10 +50,13 @@ end it "allows a supervisor in the same org to edit a volunteers phone number" do + org = create :casa_org supervisor = build_stubbed :supervisor, casa_org: org enable_pundit(view, supervisor) + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(supervisor) allow(view).to receive(:current_organization).and_return(supervisor.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -59,8 +68,11 @@ it "does not allow a supervisor from a different org to edit a volunteers phone number" do different_supervisor = build_stubbed :supervisor enable_pundit(view, different_supervisor) + org = create :casa_org + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(different_supervisor) allow(view).to receive(:current_organization).and_return(different_supervisor.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -72,8 +84,11 @@ it "shows invite and login info" do supervisor = build_stubbed :supervisor enable_pundit(view, supervisor) + org = create :casa_org + volunteer = create :volunteer, casa_org: org allow(view).to receive(:current_user).and_return(supervisor) allow(view).to receive(:current_organization).and_return(supervisor.casa_org) + assign :volunteer, volunteer assign :supervisors, [] @@ -87,34 +102,40 @@ expect(rendered).to have_text("Learning Hours This Year\n 0h 0min") end - context " the user has requested to reset their password" do - describe "shows resend invitation " - let(:volunteer) { create :volunteer } - let(:supervisor) { build_stubbed :supervisor } - let(:admin) { build_stubbed :casa_admin } + context "the user has requested to reset their password" do + describe "shows resend invitation" do + it "allows an administrator resend invitation to a volunteer" do + volunteer = create :volunteer + supervisor = build_stubbed :supervisor + admin = build_stubbed :casa_admin - it "allows an administrator resend invitation to a volunteer" do - enable_pundit(view, supervisor) - allow(view).to receive(:current_user).and_return(admin) - allow(view).to receive(:current_organization).and_return(admin.casa_org) - assign :volunteer, volunteer - assign :supervisors, [] + enable_pundit(view, supervisor) + allow(view).to receive(:current_user).and_return(admin) + allow(view).to receive(:current_organization).and_return(admin.casa_org) - render template: "volunteers/edit" + assign :volunteer, volunteer + assign :supervisors, [] - expect(rendered).to have_content("Resend Invitation") - end + render template: "volunteers/edit" + + expect(rendered).to have_content("Resend Invitation") + end + + it "allows a supervisor to resend invitation to a volunteer" do + volunteer = create :volunteer + supervisor = build_stubbed :supervisor + + enable_pundit(view, supervisor) + allow(view).to receive(:current_user).and_return(supervisor) + allow(view).to receive(:current_organization).and_return(supervisor.casa_org) - it "allows a supervisor to resend invitation to a volunteer" do - enable_pundit(view, supervisor) - allow(view).to receive(:current_user).and_return(supervisor) - allow(view).to receive(:current_organization).and_return(supervisor.casa_org) - assign :volunteer, volunteer - assign :supervisors, [] + assign :volunteer, volunteer + assign :supervisors, [] - render template: "volunteers/edit" + render template: "volunteers/edit" - expect(rendered).to have_content("Resend Invitation") + expect(rendered).to have_content("Resend Invitation") + end end end end diff --git a/yarn.lock b/yarn.lock index f613463aee..6e6d40c45e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,14 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" @@ -24,10 +32,31 @@ dependencies: "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" - integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== +"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + +"@babel/core@^7.0.0": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.8" + "@babel/types" "^7.22.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.1" "@babel/core@^7.11.6", "@babel/core@^7.12.3": version "7.19.1" @@ -69,6 +98,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" @@ -90,16 +129,16 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.19.1", "@babel/helper-compilation-targets@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" - integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== +"@babel/helper-compilation-targets@^7.19.1", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== dependencies: - "@babel/compat-data" "^7.22.5" + "@babel/compat-data" "^7.22.9" "@babel/helper-validator-option" "^7.22.5" - browserslist "^4.21.3" + browserslist "^4.21.9" lru-cache "^5.1.1" - semver "^6.3.0" + semver "^6.3.1" "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" @@ -133,17 +172,16 @@ regexpu-core "^5.3.1" semver "^6.3.0" -"@babel/helper-define-polyfill-provider@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8" - integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg== +"@babel/helper-define-polyfill-provider@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz#af1429c4a83ac316a6a8c2cc8ff45cb5d2998d3a" + integrity sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A== dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" - semver "^6.1.2" "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" @@ -234,6 +272,17 @@ "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" +"@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -241,7 +290,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== @@ -303,6 +352,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + "@babel/helper-string-parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" @@ -342,6 +398,15 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" +"@babel/helpers@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" + integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== + dependencies: + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.6" + "@babel/types" "^7.22.5" + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" @@ -370,6 +435,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== +"@babel/parser@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" @@ -554,10 +624,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz#7336356d23380eda9a56314974f053a020dab0c3" - integrity sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg== +"@babel/plugin-transform-async-generator-functions@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b" + integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" @@ -604,19 +674,19 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" - integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== +"@babel/plugin-transform-classes@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" + integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.22.5": @@ -812,10 +882,10 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz#1003762b9c14295501beb41be72426736bedd1e0" - integrity sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ== +"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" + integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -935,13 +1005,13 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/preset-env@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.5.tgz#3da66078b181f3d62512c51cf7014392c511504e" - integrity sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A== +"@babel/preset-env@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" + integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.22.5" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" @@ -966,13 +1036,13 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.7" "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" "@babel/plugin-transform-block-scoping" "^7.22.5" "@babel/plugin-transform-class-properties" "^7.22.5" "@babel/plugin-transform-class-static-block" "^7.22.5" - "@babel/plugin-transform-classes" "^7.22.5" + "@babel/plugin-transform-classes" "^7.22.6" "@babel/plugin-transform-computed-properties" "^7.22.5" "@babel/plugin-transform-destructuring" "^7.22.5" "@babel/plugin-transform-dotall-regex" "^7.22.5" @@ -997,7 +1067,7 @@ "@babel/plugin-transform-object-rest-spread" "^7.22.5" "@babel/plugin-transform-object-super" "^7.22.5" "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.6" "@babel/plugin-transform-parameters" "^7.22.5" "@babel/plugin-transform-private-methods" "^7.22.5" "@babel/plugin-transform-private-property-in-object" "^7.22.5" @@ -1015,11 +1085,11 @@ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "^0.1.5" "@babel/types" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.3" - babel-plugin-polyfill-corejs3 "^0.8.1" - babel-plugin-polyfill-regenerator "^0.5.0" - core-js-compat "^3.30.2" - semver "^6.3.0" + babel-plugin-polyfill-corejs2 "^0.4.4" + babel-plugin-polyfill-corejs3 "^0.8.2" + babel-plugin-polyfill-regenerator "^0.5.1" + core-js-compat "^3.31.0" + semver "^6.3.1" "@babel/preset-modules@^0.1.5": version "0.1.5" @@ -1062,7 +1132,7 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1": version "7.19.1" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz" integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== @@ -1094,6 +1164,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": + version "7.22.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.7" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.22.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" @@ -1108,115 +1194,115 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@esbuild/android-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz#fa6f0cc7105367cb79cc0a8bf32bf50cb1673e45" - integrity sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw== - -"@esbuild/android-arm@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.11.tgz#ae84a410696c9f549a15be94eaececb860bacacb" - integrity sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q== - -"@esbuild/android-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.11.tgz#0e58360bbc789ad0d68174d32ba20e678c2a16b6" - integrity sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw== - -"@esbuild/darwin-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz#fcdcd2ef76ca656540208afdd84f284072f0d1f9" - integrity sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w== - -"@esbuild/darwin-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz#c5ac602ec0504a8ff81e876bc8a9811e94d69d37" - integrity sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw== - -"@esbuild/freebsd-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz#7012fb06ee3e6e0d5560664a65f3fefbcc46db2e" - integrity sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A== - -"@esbuild/freebsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz#c5de1199f70e1f97d5c8fca51afa9bf9a2af5969" - integrity sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q== - -"@esbuild/linux-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz#2a6d3a74e0b8b5f294e22b4515b29f76ebd42660" - integrity sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog== - -"@esbuild/linux-arm@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz#5175bd61b793b436e4aece6328aa0d9be07751e1" - integrity sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg== - -"@esbuild/linux-ia32@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz#20ee6cfd65a398875f321a485e7b2278e5f6f67b" - integrity sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw== - -"@esbuild/linux-loong64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz#8e7b251dede75083bf44508dab5edce3f49d052b" - integrity sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw== - -"@esbuild/linux-mips64el@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz#a3125eb48538ac4932a9d05089b157f94e443165" - integrity sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg== - -"@esbuild/linux-ppc64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz#842abadb7a0995bd539adee2be4d681b68279499" - integrity sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ== - -"@esbuild/linux-riscv64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz#7ce6e6cee1c72d5b4d2f4f8b6fcccf4a9bea0e28" - integrity sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w== - -"@esbuild/linux-s390x@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz#98fbc794363d02ded07d300df2e535650b297b96" - integrity sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg== - -"@esbuild/linux-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz#f8458ec8cf74c8274e4cacd00744d8446cac52eb" - integrity sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA== - -"@esbuild/netbsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz#a7b2f991b8293748a7be42eac1c4325faf0c7cca" - integrity sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q== - -"@esbuild/openbsd-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz#3e50923de84c54008f834221130fd23646072b2f" - integrity sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ== - -"@esbuild/sunos-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz#ae47a550b0cd395de03606ecfba03cc96c7c19e2" - integrity sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng== - -"@esbuild/win32-arm64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz#05d364582b7862d7fbf4698ef43644f7346dcfcc" - integrity sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg== - -"@esbuild/win32-ia32@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz#a3372095a4a1939da672156a3c104f8ce85ee616" - integrity sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg== - -"@esbuild/win32-x64@0.18.11": - version "0.18.11" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz#6526c7e1b40d5b9f0a222c6b767c22f6fb97aa57" - integrity sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA== +"@esbuild/android-arm64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz#9e00eb6865ed5f2dbe71a1e96f2c52254cd92903" + integrity sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg== + +"@esbuild/android-arm@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.17.tgz#1aa013b65524f4e9f794946b415b32ae963a4618" + integrity sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg== + +"@esbuild/android-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.17.tgz#c2bd0469b04ded352de011fae34a7a1d4dcecb79" + integrity sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw== + +"@esbuild/darwin-arm64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz#0c21a59cb5bd7a2cec66c7a42431dca42aefeddd" + integrity sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g== + +"@esbuild/darwin-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz#92f8763ff6f97dff1c28a584da7b51b585e87a7b" + integrity sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g== + +"@esbuild/freebsd-arm64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz#934f74bdf4022e143ba2f21d421b50fd0fead8f8" + integrity sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ== + +"@esbuild/freebsd-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz#16b6e90ba26ecc865eab71c56696258ec7f5d8bf" + integrity sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA== + +"@esbuild/linux-arm64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz#179a58e8d4c72116eb068563629349f8f4b48072" + integrity sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ== + +"@esbuild/linux-arm@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz#9d78cf87a310ae9ed985c3915d5126578665c7b5" + integrity sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg== + +"@esbuild/linux-ia32@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz#6fed202602d37361bca376c9d113266a722a908c" + integrity sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg== + +"@esbuild/linux-loong64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz#cdc60304830be1e74560c704bfd72cab8a02fa06" + integrity sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg== + +"@esbuild/linux-mips64el@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz#c367b2855bb0902f5576291a2049812af2088086" + integrity sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ== + +"@esbuild/linux-ppc64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz#7fdc0083d42d64a4651711ee0a7964f489242f45" + integrity sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ== + +"@esbuild/linux-riscv64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz#5198a417f3f5b86b10c95647b8bc032e5b6b2b1c" + integrity sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g== + +"@esbuild/linux-s390x@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz#7459c2fecdee2d582f0697fb76a4041f4ad1dd1e" + integrity sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg== + +"@esbuild/linux-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz#948cdbf46d81c81ebd7225a7633009bc56a4488c" + integrity sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ== + +"@esbuild/netbsd-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz#6bb89668c0e093c5a575ded08e1d308bd7fd63e7" + integrity sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ== + +"@esbuild/openbsd-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz#abac2ae75fef820ef6c2c48da4666d092584c79d" + integrity sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA== + +"@esbuild/sunos-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz#74a45fe1db8ea96898f1a9bb401dcf1dadfc8371" + integrity sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g== + +"@esbuild/win32-arm64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz#fd95ffd217995589058a4ed8ac17ee72a3d7f615" + integrity sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw== + +"@esbuild/win32-ia32@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz#9b7ef5d0df97593a80f946b482e34fcba3fa4aaf" + integrity sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg== + +"@esbuild/win32-x64@0.18.17": + version "0.18.17" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz#bcb2e042631b3c15792058e189ed879a22b2968b" + integrity sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1267,6 +1353,11 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hotwired/stimulus@^3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.1.tgz#e3de23623b0c52c247aba4cd5d530d257008676b" + integrity sha512-HGlzDcf9vv/EQrMJ5ZG6VWNs8Z/xMN+1o2OhV1gKiSG6CqZt5MCBB1gRg5ILiN3U0jEAxuDTNPRfBcnZBDmupQ== + "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -1302,28 +1393,28 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== +"@jest/console@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.2.tgz#bf1d4101347c23e07c029a1b1ae07d550f5cc541" + integrity sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +"@jest/core@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8" + integrity sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg== + dependencies: + "@jest/console" "^29.6.2" + "@jest/reporters" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" @@ -1331,81 +1422,81 @@ exit "^0.1.2" graceful-fs "^4.2.9" jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" + jest-config "^29.6.2" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" + jest-resolve "^29.6.2" + jest-resolve-dependencies "^29.6.2" + jest-runner "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + jest-watcher "^29.6.2" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== +"@jest/environment@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9" + integrity sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q== dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.5.0" + jest-mock "^29.6.2" -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== +"@jest/expect-utils@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" + integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== dependencies: jest-get-type "^29.4.3" -"@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== +"@jest/expect@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28" + integrity sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg== dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" + expect "^29.6.2" + jest-snapshot "^29.6.2" -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== +"@jest/fake-timers@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4" + integrity sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-util "^29.6.2" -"@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== +"@jest/globals@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a" + integrity sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/types" "^29.6.1" + jest-mock "^29.6.2" -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== +"@jest/reporters@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a" + integrity sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -1417,77 +1508,77 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + jest-worker "^29.6.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== +"@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== dependencies: - "@sinclair/typebox" "^0.25.16" + "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== +"@jest/test-result@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed" + integrity sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw== dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.2" + "@jest/types" "^29.6.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== +"@jest/test-sequencer@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4" + integrity sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw== dependencies: - "@jest/test-result" "^29.5.0" + "@jest/test-result" "^29.6.2" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.2" slash "^3.0.0" -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== +"@jest/transform@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3" + integrity sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.2" jest-regex-util "^29.4.3" - jest-util "^29.5.0" + jest-util "^29.6.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== +"@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.0" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -1502,6 +1593,15 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" @@ -1526,7 +1626,7 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== @@ -1542,11 +1642,24 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.18": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@kurkle/color@^0.3.0": version "0.3.2" resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== +"@nicolo-ribaudo/semver-v6@^6.3.3": + version "6.3.3" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29" + integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -1578,7 +1691,14 @@ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.6.tgz#60765c8e9c357a0ba0840fb1e409caedc233ea0d" integrity sha512-ybBsUrIsu5geM8BtqnpM7ZA9D8uzSz+e1B4JR57NaCmasHKWap6AX5DT7NHIbp21opVet1qqoVSdsoLDqXeB2A== -"@rails/activestorage@^7.0.6": +"@rails/actiontext@^7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@rails/actiontext/-/actiontext-7.0.6.tgz#42b2b275ce6b24031242c731828de9c02d58b53f" + integrity sha512-WHHkYmypUjlo6fvNxfKRluuHmIxZCEoK+g/hBsufoxvjvWSBx61qu0Xq0tCrGUYd4mUZ/kk8OqP96/UBI3vS/w== + dependencies: + "@rails/activestorage" ">= 7.0.0-alpha1" + +"@rails/activestorage@>= 7.0.0-alpha1", "@rails/activestorage@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-7.0.6.tgz#8b762e7142eb8332541dffa0f841d7a5026f64fd" integrity sha512-sLr5Uj8cnWkX9lLV7wk8mIVFt6XFOJR7mAXm0QmDnrR/YGMF4Ho5tcVRUHzlyAisRQcHp6D0zHSVgOUukF8OHg== @@ -1607,10 +1727,10 @@ resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== -"@sinclair/typebox@^0.25.16": - version "0.25.21" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" - integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^2.0.0": version "2.0.0" @@ -1709,11 +1829,6 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz" integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== -"@types/prettier@^2.1.5": - version "2.7.0" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz" - integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" @@ -1953,12 +2068,12 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== +babel-jest@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126" + integrity sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A== dependencies: - "@jest/transform" "^29.5.0" + "@jest/transform" "^29.6.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" babel-preset-jest "^29.5.0" @@ -1987,29 +2102,29 @@ babel-plugin-jest-hoist@^29.5.0: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd" - integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw== +babel-plugin-polyfill-corejs2@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz#9f9a0e1cd9d645cc246a5e094db5c3aa913ccd2b" + integrity sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA== dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.4.0" - semver "^6.1.1" + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.4.1" + "@nicolo-ribaudo/semver-v6" "^6.3.3" -babel-plugin-polyfill-corejs3@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a" - integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q== +babel-plugin-polyfill-corejs3@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz#d406c5738d298cd9c66f64a94cf8d5904ce4cc5e" + integrity sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.0" - core-js-compat "^3.30.1" + "@babel/helper-define-polyfill-provider" "^0.4.1" + core-js-compat "^3.31.0" -babel-plugin-polyfill-regenerator@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380" - integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g== +babel-plugin-polyfill-regenerator@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz#ace7a5eced6dff7d5060c335c52064778216afd3" + integrity sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.0" + "@babel/helper-define-polyfill-provider" "^0.4.1" babel-preset-current-node-syntax@^1.0.0: version "1.0.1" @@ -2076,10 +2191,10 @@ bootstrap-select@^1.13.18: resolved "https://registry.npmjs.org/bootstrap-select/-/bootstrap-select-1.13.18.tgz" integrity sha512-V1IzK4rxBq5FrJtkzSH6RmFLFBsjx50byFbfAf8jYyXROWs7ZpprGjdHeoyq2HSsHyjJhMMwjsQhRoYAfxCGow== -bootstrap@5.3.0, bootstrap@^5.1.3: - version "5.3.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0.tgz#0718a7cc29040ee8dbf1bd652b896f3436a87c29" - integrity sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw== +bootstrap@5.3.1, bootstrap@^5.1.3: + version "5.3.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.1.tgz#8ca07040ad15d7f75891d1504cf14c5dedfb1cfe" + integrity sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g== brace-expansion@^1.1.7: version "1.1.11" @@ -2101,23 +2216,13 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.21.3: - version "4.21.4" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== - dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" - -browserslist@^4.21.5: - version "4.21.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551" - integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA== +browserslist@^4.21.9: + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== dependencies: - caniuse-lite "^1.0.30001489" - electron-to-chromium "^1.4.411" + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" node-releases "^2.0.12" update-browserslist-db "^1.0.11" @@ -2163,10 +2268,10 @@ camelcase@^6.2.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001489: - version "1.0.30001494" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001494.tgz" - integrity sha512-sY2B5Qyl46ZzfYDegrl8GBCzdawSLT4ThM9b9F+aDYUrAG2zCOyMbd2Tq34mS1g4ZKBfjRlzOohQMxx28x6wJg== +caniuse-lite@^1.0.30001503: + version "1.0.30001515" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b" + integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA== chalk@^2.0.0: version "2.4.2" @@ -2190,10 +2295,10 @@ char-regex@^1.0.2: resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chart.js@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.3.0.tgz#ac363030ab3fec572850d2d872956f32a46326a1" - integrity sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g== +chart.js@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.3.2.tgz#904ebe0376eb368a659a92d2050df47336847e4c" + integrity sha512-pvQNyFOY1QmbmIr8oDORL16/FFivfxj8V26VFpFilMo4cNvkV5WXLJetDio365pd9gKUHGdirUTbqJfw8tr+Dg== dependencies: "@kurkle/color" "^0.3.0" @@ -2321,12 +2426,12 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-js-compat@^3.30.1, core-js-compat@^3.30.2: - version "3.30.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b" - integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA== +core-js-compat@^3.31.0: + version "3.31.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.1.tgz#5084ad1a46858df50ff89ace152441a63ba7aae0" + integrity sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA== dependencies: - browserslist "^4.21.5" + browserslist "^4.21.9" core-util-is@~1.0.0: version "1.0.3" @@ -2368,18 +2473,18 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -datatables.net-dt@^1.13.4: - version "1.13.4" - resolved "https://registry.yarnpkg.com/datatables.net-dt/-/datatables.net-dt-1.13.4.tgz#ec32d22a02772ee6dda2677032cb6b24f3f5e4d0" - integrity sha512-QAvuEej/qKSiaSmSeDQ36wWO72XzFGKkd0jdiqbp+2FHAAzIk+ffsqQAwylystMoBSiO0zlcdaqHoAPa5Dy7Pg== +datatables.net-dt@^1.13.6: + version "1.13.6" + resolved "https://registry.yarnpkg.com/datatables.net-dt/-/datatables.net-dt-1.13.6.tgz#da646d9e288ba690393a8bdf129916a038d14b3c" + integrity sha512-0fBsUi8k5e+x5e+xA/Eb5stFr2PIkHgDnbhZs8ZDLvzzL975lCm6sqBAcsTsXKvF7yuBvaDTVBTF4wOMw7PrYw== dependencies: - datatables.net ">=1.12.1" + datatables.net ">=1.13.4" jquery ">=1.7" -datatables.net@>=1.12.1: - version "1.12.1" - resolved "https://registry.npmjs.org/datatables.net/-/datatables.net-1.12.1.tgz" - integrity sha512-e6XAMUoV41JdQPS/r9YRfRcmTPcCVvyZbWI+xog1Zg+kjVliMQbEkvWK5XFItmi64Cvwg+IqsZbTUJ1KSY3umA== +datatables.net@>=1.13.4: + version "1.13.5" + resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.5.tgz#790a3d70d5e103f5465ed8c52a50eb242e1e2dc4" + integrity sha512-XoCQHkUM5MwbC3Wx7WpVvt4i880J8pIFDA9HIKD4GhvtalryBfmdd+bZvrc/rEbraZS7U4eR2k8/wFY0NeHVqQ== dependencies: jquery ">=1.7" @@ -2402,10 +2507,10 @@ decimal.js@^10.3.1: resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.1.tgz" integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" @@ -2471,15 +2576,10 @@ duplexer@~0.1.1: resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -electron-to-chromium@^1.4.251: - version "1.4.256" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz" - integrity sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw== - -electron-to-chromium@^1.4.411: - version "1.4.413" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.413.tgz#0067c3122946ae234cbefb9401ecefde851cdcf2" - integrity sha512-Gd+/OAhRca06dkVxIQo/W7dr6Nmk9cx6lQdZ19GvFp51k5B/lUAokm6SJfNkdV8kFLsC3Z4sLTyEHWCnB1Efbw== +electron-to-chromium@^1.4.431: + version "1.4.457" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.457.tgz#3fdc7b4f97d628ac6b51e8b4b385befb362fe343" + integrity sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA== emittery@^0.13.1: version "0.13.1" @@ -2597,33 +2697,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.18.11: - version "0.18.11" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.11.tgz#cbf94dc3359d57f600a0dbf281df9b1d1b4a156e" - integrity sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA== +esbuild@^0.18.17: + version "0.18.17" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.17.tgz#2aaf6bc6759b0c605777fdc435fea3969e091cad" + integrity sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg== optionalDependencies: - "@esbuild/android-arm" "0.18.11" - "@esbuild/android-arm64" "0.18.11" - "@esbuild/android-x64" "0.18.11" - "@esbuild/darwin-arm64" "0.18.11" - "@esbuild/darwin-x64" "0.18.11" - "@esbuild/freebsd-arm64" "0.18.11" - "@esbuild/freebsd-x64" "0.18.11" - "@esbuild/linux-arm" "0.18.11" - "@esbuild/linux-arm64" "0.18.11" - "@esbuild/linux-ia32" "0.18.11" - "@esbuild/linux-loong64" "0.18.11" - "@esbuild/linux-mips64el" "0.18.11" - "@esbuild/linux-ppc64" "0.18.11" - "@esbuild/linux-riscv64" "0.18.11" - "@esbuild/linux-s390x" "0.18.11" - "@esbuild/linux-x64" "0.18.11" - "@esbuild/netbsd-x64" "0.18.11" - "@esbuild/openbsd-x64" "0.18.11" - "@esbuild/sunos-x64" "0.18.11" - "@esbuild/win32-arm64" "0.18.11" - "@esbuild/win32-ia32" "0.18.11" - "@esbuild/win32-x64" "0.18.11" + "@esbuild/android-arm" "0.18.17" + "@esbuild/android-arm64" "0.18.17" + "@esbuild/android-x64" "0.18.17" + "@esbuild/darwin-arm64" "0.18.17" + "@esbuild/darwin-x64" "0.18.17" + "@esbuild/freebsd-arm64" "0.18.17" + "@esbuild/freebsd-x64" "0.18.17" + "@esbuild/linux-arm" "0.18.17" + "@esbuild/linux-arm64" "0.18.17" + "@esbuild/linux-ia32" "0.18.17" + "@esbuild/linux-loong64" "0.18.17" + "@esbuild/linux-mips64el" "0.18.17" + "@esbuild/linux-ppc64" "0.18.17" + "@esbuild/linux-riscv64" "0.18.17" + "@esbuild/linux-s390x" "0.18.17" + "@esbuild/linux-x64" "0.18.17" + "@esbuild/netbsd-x64" "0.18.17" + "@esbuild/openbsd-x64" "0.18.17" + "@esbuild/sunos-x64" "0.18.17" + "@esbuild/win32-arm64" "0.18.17" + "@esbuild/win32-ia32" "0.18.17" + "@esbuild/win32-x64" "0.18.17" escalade@^3.1.1: version "3.1.1" @@ -2917,16 +3017,17 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -expect@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== +expect@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" + integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== dependencies: - "@jest/expect-utils" "^29.5.0" + "@jest/expect-utils" "^29.6.2" + "@types/node" "*" jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" extend-shallow@^2.0.1: version "2.0.1" @@ -3655,87 +3756,87 @@ jest-changed-files@^29.5.0: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258" + integrity sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.6.2" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.2" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda" + integrity sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-config "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3" + integrity sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.6.2" + "@jest/types" "^29.6.1" + babel-jest "^29.6.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" + jest-circus "^29.6.2" + jest-environment-node "^29.6.2" jest-get-type "^29.4.3" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-resolve "^29.6.2" + jest-runner "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.6.2" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== +jest-diff@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" + integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== dependencies: chalk "^4.0.0" diff-sequences "^29.4.3" jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.2" jest-docblock@^29.4.3: version "29.4.3" @@ -3744,108 +3845,108 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-each@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce" + integrity sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" chalk "^4.0.0" jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" + jest-util "^29.6.2" + pretty-format "^29.6.2" -jest-environment-jsdom@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz#cfe86ebaf1453f3297b5ff3470fbe94739c960cb" - integrity sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw== +jest-environment-jsdom@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.6.2.tgz#4fc68836a7774a771819a2f980cb47af3b1629da" + integrity sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.6.2" + jest-util "^29.6.2" jsdom "^20.0.0" -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== +jest-environment-node@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad" + integrity sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ== dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.6.2" + jest-util "^29.6.2" jest-get-type@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-haste-map@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1" + integrity sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-util "^29.6.2" + jest-worker "^29.6.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38" + integrity sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ== dependencies: jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.2" -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== +jest-matcher-utils@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" + integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== dependencies: chalk "^4.0.0" - jest-diff "^29.5.0" + jest-diff "^29.6.2" jest-get-type "^29.4.3" - pretty-format "^29.5.0" + pretty-format "^29.6.2" -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== +jest-message-util@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" + integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.2" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-mock@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00" + integrity sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.2" jest-pnp-resolver@^1.2.2: version "1.2.2" @@ -3857,170 +3958,167 @@ jest-regex-util@^29.4.3: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2" + integrity sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w== dependencies: jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-snapshot "^29.6.2" -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838" + integrity sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.2" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.6.2" + jest-validate "^29.6.2" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runner@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01" + integrity sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w== + dependencies: + "@jest/console" "^29.6.2" + "@jest/environment" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-environment-node "^29.6.2" + jest-haste-map "^29.6.2" + jest-leak-detector "^29.6.2" + jest-message-util "^29.6.2" + jest-resolve "^29.6.2" + jest-runtime "^29.6.2" + jest-util "^29.6.2" + jest-watcher "^29.6.2" + jest-worker "^29.6.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runtime@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545" + integrity sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/globals" "^29.6.2" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-resolve "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c" + integrity sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.6.2" graceful-fs "^4.2.9" - jest-diff "^29.5.0" + jest-diff "^29.6.2" jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.6.2" + semver "^7.5.3" -jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== +jest-util@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" + integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-validate@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082" + integrity sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.1" camelcase "^6.2.0" chalk "^4.0.0" jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.2" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088" + integrity sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.6.2" string-length "^4.0.1" -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== +jest-worker@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.2.tgz#682fbc4b6856ad0aa122a5403c6d048b83f3fb44" + integrity sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ== dependencies: "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.2" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== +jest@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42" + integrity sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.2" + "@jest/types" "^29.6.1" import-local "^3.0.2" - jest-cli "^29.5.0" + jest-cli "^29.6.2" joi@^17.7.0: version "17.7.0" @@ -4138,6 +4236,11 @@ json5@^2.2.1: resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jstz@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jstz/-/jstz-2.1.1.tgz#fff3373a518fa7cce69299930466f5a2b980389d" @@ -4432,11 +4535,6 @@ node-releases@^2.0.12: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -4718,12 +4816,12 @@ prelude-ls@~1.1.2: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== +pretty-format@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" + integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.0" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -5012,10 +5110,10 @@ safe-regex-test@^1.0.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass@^1.63.6: - version "1.63.6" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea" - integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw== +sass@^1.64.2: + version "1.64.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.64.2.tgz#0d9805ad6acf31c59c3acc725fcfb91b7fcc6909" + integrity sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5040,15 +5138,15 @@ select2@^4.0.13: resolved "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz" integrity sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.3.5, semver@^7.3.8: - version "7.5.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== +semver@^7.0.0, semver@^7.3.8, semver@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -5176,6 +5274,11 @@ start-server-and-test@^2.0.0: ps-tree "1.2.0" wait-on "7.0.1" +stimulus-rails-nested-form@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/stimulus-rails-nested-form/-/stimulus-rails-nested-form-4.1.0.tgz#bfce185cff908170a4eb9973875b72517c3bc83a" + integrity sha512-ORqcTsg3sa4PGFEyUkbvcPG56F4K2fx1qJCUQIgngO1GaW5taKcvDkT0HvdTqtQAFe/1lN4CpJAqoSCt+nYF/Q== + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz" @@ -5412,6 +5515,11 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +trix@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/trix/-/trix-2.0.5.tgz#f54880ab38e24d47829a04e04afaf03a8607f772" + integrity sha512-OiCbDf17F7JahEwhyL1MvK9DxAAT1vkaW5sn+zpwfemZAcc4RfQB4ku18/1mKP58LRwBhjcy+6TBho7ciXz52Q== + tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz" @@ -5531,14 +5639,6 @@ update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" -update-browserslist-db@^1.0.9: - version "1.0.9" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz" - integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -5661,9 +5761,9 @@ which@^2.0.1: isexe "^2.0.0" word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== wrap-ansi@^7.0.0: version "7.0.0"