diff --git a/.circleci/config.yml b/.circleci/config.yml index 31a78778a..4a7f1666d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ version: 2 jobs: build: docker: - - image: cimg/ruby:3.1.0-browsers + - image: cimg/ruby:3.1.1-browsers environment: RAILS_ENV: test PGHOST: 127.0.0.1 diff --git a/Dockerfile b/Dockerfile index 57fb774bc..bdf71c9e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.1.0-slim +FROM ruby:3.1.1-slim RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ diff --git a/Gemfile b/Gemfile index 68ed2e656..b77bf6698 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '3.1.0' +ruby '3.1.1' # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.2", ">= 7.0.2.3" diff --git a/Gemfile.lock b/Gemfile.lock index 8e80aec35..f9eb5b5b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -326,8 +326,6 @@ GEM rack (>= 2.0.0) rack-protection (2.2.0) rack - rack-proxy (0.7.2) - rack rack-test (1.1.0) rack (>= 1.0, < 3) rails (7.0.2.3) @@ -430,7 +428,6 @@ GEM childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) - semantic_range (3.0.0) sidekiq (6.4.2) connection_pool (>= 2.2.2) rack (~> 2.0) @@ -544,7 +541,7 @@ DEPENDENCIES whenever RUBY VERSION - ruby 3.1.0p0 + ruby 3.1.1p18 BUNDLED WITH 2.3.7 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index a6ea8c168..09e1ae4ef 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -2,3 +2,5 @@ //= link_directory ../javascripts .js //= link_directory ../stylesheets .css //= link_directory ../stylesheets .scss +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/app.js similarity index 100% rename from app/assets/javascripts/application.js rename to app/assets/javascripts/app.js diff --git a/app/javascript/application.js b/app/javascript/application.js index ae4e4c138..067d33856 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,2 @@ -import "./controllers" - -console.log("Rails application js"); +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +//= require controllers diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js index b01591400..777b19866 100644 --- a/app/javascript/controllers/application.js +++ b/app/javascript/controllers/application.js @@ -6,6 +6,6 @@ const application = Application.start() application.debug = false window.Stimulus = application -console.log("Stimlus app controller"); +console.log("Stimulus app controller"); export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js index ec5598279..f34eb5455 100644 --- a/app/javascript/controllers/hello_controller.js +++ b/app/javascript/controllers/hello_controller.js @@ -2,6 +2,6 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { - console.log("Stimulus Hello World!") + this.element.textContent = "Hello from Stimulus!" } } diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index d0685d3b7..54ad4cad4 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -1,8 +1,11 @@ -// 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 and register all your controllers from the importmap under controllers/* -import { application } from "./application" +import { application } from "controllers/application" -import HelloController from "./hello_controller" -application.register("hello", HelloController) +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/app/models/collection.rb b/app/models/collection.rb index 7b8d55bab..2f80146eb 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -10,7 +10,7 @@ class Collection < ApplicationRecord validates :quarter, presence: true validates :name, presence: true - validates :reflection, length: { maximum: 1000 } + validates :reflection, length: { maximum: 5000 } def omb_control_number "omb_control_number" diff --git a/app/views/components/forms/_custom.html.erb b/app/views/components/forms/_custom.html.erb index 32d94f171..36949b0fa 100644 --- a/app/views/components/forms/_custom.html.erb +++ b/app/views/components/forms/_custom.html.erb @@ -101,42 +101,3 @@ <% end %> - diff --git a/app/views/components/forms/question_types/_text_email_field.html.erb b/app/views/components/forms/question_types/_text_email_field.html.erb index ef1994b8c..4ca119a89 100644 --- a/app/views/components/forms/question_types/_text_email_field.html.erb +++ b/app/views/components/forms/question_types/_text_email_field.html.erb @@ -2,19 +2,3 @@ <%= render 'components/question_title_label', question: question %> <%= text_field_tag question.answer_field.to_sym, nil, type: :email, class: "usa-input", required: question.is_required %> - diff --git a/app/views/components/forms/question_types/_text_phone_field.html.erb b/app/views/components/forms/question_types/_text_phone_field.html.erb index f65c3279c..49e7c65f6 100644 --- a/app/views/components/forms/question_types/_text_phone_field.html.erb +++ b/app/views/components/forms/question_types/_text_phone_field.html.erb @@ -2,14 +2,3 @@ <%= render 'components/question_title_label', question: question %> <%= telephone_field_tag question.answer_field.to_sym, nil, class: "usa-input", required: question.is_required, maxlength: 10 %> - diff --git a/app/views/components/widget/_fba.js.erb b/app/views/components/widget/_fba.js.erb index 3d83d4f5f..106471ec2 100644 --- a/app/views/components/widget/_fba.js.erb +++ b/app/views/components/widget/_fba.js.erb @@ -28,7 +28,6 @@ function FBAform(d, N) { }, init: function(options) { this.javscriptIsEnabled(); - this.options = options; <% if form.load_css %> this.loadCss(); @@ -93,12 +92,15 @@ function FBAform(d, N) { d.body.appendChild(this.dialogEl); d.querySelector('.fba-modal-close').addEventListener('click', this.handleDialogClose.bind(this), false); - var otherElements = d.querySelectorAll(".usa-input.other-option"); - for (var i = 0; i < otherElements.length; i++) { - otherElements[i].addEventListener('keyup', this.handleOtherOption.bind(this), false); - } <% end %> - + var otherElements = d.querySelectorAll(".usa-input.other-option"); + for (var i = 0; i < otherElements.length; i++) { + otherElements[i].addEventListener('keyup', this.handleOtherOption.bind(this), false); + } + var phoneElements = d.querySelectorAll("input[type='tel']"); + for (var i = 0; i < phoneElements.length; i++) { + phoneElements[i].addEventListener('keyup', this.handlePhoneInput.bind(this), false); + } <%# add button behaviors for custom-button-modal %> <% if form.delivery_method == "custom-button-modal" %> <% if form.element_selector? %> @@ -169,6 +171,29 @@ function FBAform(d, N) { option.checked = true; option.value = other_val; }, + handlePhoneInput: function(e) { + var number = e.srcElement.value.replace(/[^\d]/g, ''); + if (number.length == 7) { + number = number.replace(/(\d{3})(\d{4})/, "$1-$2"); + } else if (number.length == 10) { + number = number.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3"); + } + e.srcElement.value = number; + }, + handleEmailInput: function(e) { + var EmailRegex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; + var email = e.srcElement.value.trim(); + if (email.length == 0) { + return; + } + result = EmailRegex.test(email); + if (!result) { + showWarning($(this),"Please enter a valid email address"); + } else { + showValid($(this)); + } + e.srcElement.value = number; + }, handleSubmitClick: function(e) { e.preventDefault(); this.resetErrors(); @@ -493,7 +518,6 @@ function FBAform(d, N) { } alertErrorElement.removeAttribute("hidden"); } else { // SERVER ERROR - console.log('failed'); alertErrorElement.removeAttribute("hidden"); alertErrorElementBody.innerHTML += "Server error. We're sorry, but this submission was not successful. The Product Team has been notified."; } diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 442bb8b70..43119510f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -16,8 +16,9 @@ <%= csp_meta_tag %> <%= favicon_link_tag asset_path('favicon.ico') %> <%= stylesheet_link_tag 'application', media: 'all', integrity: true %> - <%= javascript_include_tag 'application', integrity: true %> + <%= javascript_include_tag 'app', integrity: true %> <%= render 'components/analytics/script_header' %> + <%= javascript_importmap_tags %> <%= render 'components/analytics/script_body' %> diff --git a/bin/importmap b/bin/importmap new file mode 100755 index 000000000..36502ab16 --- /dev/null +++ b/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/config/importmap.rb b/config/importmap.rb new file mode 100644 index 000000000..824fe6d3a --- /dev/null +++ b/config/importmap.rb @@ -0,0 +1,6 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/spec/features/admin/omb_cx_reporting_collections_spec.rb b/spec/features/admin/omb_cx_reporting_collections_spec.rb index 3c7ab07ed..9b78aba06 100644 --- a/spec/features/admin/omb_cx_reporting_collections_spec.rb +++ b/spec/features/admin/omb_cx_reporting_collections_spec.rb @@ -73,6 +73,7 @@ travel 14.minutes click_on "Update CX Service Detail Report" + wait_for_ajax expect(find(".usa-alert__text")).to have_content("Omb cx reporting collection was successfully created.") end end diff --git a/spec/features/touchpoints_spec.rb b/spec/features/touchpoints_spec.rb index 4a3981a8e..5d2b39aca 100644 --- a/spec/features/touchpoints_spec.rb +++ b/spec/features/touchpoints_spec.rb @@ -29,7 +29,7 @@ context "SPAMBOT" do before do visit touchpoint_path(form) - page.execute_script("$('#fba_directive').val('SPAM Text')") + page.execute_script("document.getElementById('fba_directive').value = 'SPAM Text'") click_button "Submit" end @@ -123,7 +123,6 @@ all('.usa-checkbox__label').each do |checkbox_label| checkbox_label.click end - inputs = find_all("input") inputs.first.set("hi") inputs.last.set("bye") @@ -299,14 +298,14 @@ it "allows valid email address" do fill_in "answer_03", with: "test@test.com" - find("#answer_03").native.send_key :tab - expect(find("#answer_03").value).to eq("test@test.com") + click_button "Submit" + expect(page).to have_content("Thank you. Your feedback has been received.") end it "disallows invalid text input" do fill_in "answer_03", with: "test@testcom" - find("#answer_03").native.send_key :tab - expect(page).to have_content("Please enter a valid email") + click_button "Submit" + expect(page).to have_content("Please enter a valid value: Email") end end diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb index 284719b0f..6319e5748 100644 --- a/spec/models/collection_spec.rb +++ b/spec/models/collection_spec.rb @@ -1,5 +1,72 @@ require 'rails_helper' RSpec.describe Collection, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + describe "required attributes" do + context "newly created Collection" do + before do + @collection = Collection.create({}) + end + + it "requires user" do + expect(@collection.errors.messages).to have_key(:user) + expect(@collection.errors.messages[:user]).to eq(["must exist"]) + end + + it "requires organization" do + expect(@collection.errors.messages).to have_key(:organization) + expect(@collection.errors.messages[:organization]).to eq(["must exist"]) + end + + it "requires service_provider" do + expect(@collection.errors.messages).to have_key(:service_provider) + expect(@collection.errors.messages[:service_provider]).to eq(["must exist"]) + end + + it "requires name" do + expect(@collection.errors.messages).to have_key(:name) + expect(@collection.errors.messages[:name]).to eq(["can't be blank"]) + end + + it "requires year" do + expect(@collection.errors.messages).to have_key(:year) + expect(@collection.errors.messages[:year]).to eq(["can't be blank"]) + end + + it "requires quarter" do + expect(@collection.errors.messages).to have_key(:quarter) + expect(@collection.errors.messages[:quarter]).to eq(["can't be blank"]) + end + + end + end + + let(:organization) { FactoryBot.create(:organization) } + let(:user) { FactoryBot.create(:user, organization: organization) } + let(:service_provider) { FactoryBot.create(:service_provider, organization: organization) } + + describe "a minimally valid Collection" do + before do + @collection = Collection.create(name: "Test Collection", organization: organization, user: user, service_provider: service_provider, year: 2022, quarter: 2) + end + + it "requires user" do + expect(@collection.valid?).to eq(true) + end + end + + describe "reflection text that is too long" do + context do + before do + reflection_text = "longstring" * 600 # 6,000 char string + @collection = Collection.create(name: "Test Collection", organization: organization, user: user, service_provider: service_provider, year: 2022, quarter: 2, reflection: reflection_text) + end + + it "adds error, indicating 5000 character limit" do + expect(@collection.valid?).to eq(false) + expect(@collection.errors.messages[:reflection].first).to eq("is too long (maximum is 5000 characters)") + end + + end + end + end diff --git a/vendor/javascript/.keep b/vendor/javascript/.keep new file mode 100644 index 000000000..e69de29bb