From 89e217612776505281da689ca753103b99107523 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Mon, 21 Apr 2025 12:43:02 -0400 Subject: [PATCH 01/18] [#4922] handle invalid date ranges in date range input * Fixed issue where tabbing into date range field and typing invalid data bypassed Litepicker validation * Updated DateRangeHelper to catch parsing errors and show flash notice instead of raising 500 * Added custom client-side check in Stimulus controller to catch bad input on blur * Introduced global isLitePickerActive flag in application.js so that custom date range controller can avoid alerting user if Litepicker is open --- app/helpers/date_range_helper.rb | 5 +- app/helpers/ui_helper.rb | 7 +- app/javascript/application.js | 147 +++++++++++------- .../controllers/date_range_controller.js | 46 ++++++ app/views/shared/_date_range_picker.html.erb | 3 + spec/helpers/date_range_helper_spec.rb | 48 ++++++ .../date_range_picker_shared_example.rb | 58 +++++++ 7 files changed, 258 insertions(+), 56 deletions(-) create mode 100644 app/javascript/controllers/date_range_controller.js create mode 100644 spec/helpers/date_range_helper_spec.rb diff --git a/app/helpers/date_range_helper.rb b/app/helpers/date_range_helper.rb index cc73af80d8..4327f6cdb7 100644 --- a/app/helpers/date_range_helper.rb +++ b/app/helpers/date_range_helper.rb @@ -37,7 +37,10 @@ def selected_interval date_range_params.split(" - ").map do |d| Date.strptime(d, "%B %d, %Y") rescue - raise "Invalid date: #{d} in #{date_range_params}" + flash.now[:notice] = "Invalid Date range provided. Reset to default date range" + return default_date.split(" - ").map do |d| + Date.strptime(d.to_s, "%B %d, %Y") + end end end diff --git a/app/helpers/ui_helper.rb b/app/helpers/ui_helper.rb index 50ce491377..9ed262e849 100644 --- a/app/helpers/ui_helper.rb +++ b/app/helpers/ui_helper.rb @@ -110,8 +110,11 @@ def edit_button_to(link, options = {}, properties = {}) _link_to link, { icon: "pencil-square-o", type: "primary", text: "Edit", size: "xs" }.merge(options), properties end - def filter_button(options = {}) - _button_to({ icon: "filter", type: "primary", text: "Filter", size: "md" }.merge(options)) + def filter_button(options = {}, data = {}) + _button_to( + { icon: "filter", type: "primary", text: "Filter", size: "md" }.merge(options), + { data: { test_id: "filter-button" }.merge(data) } + ) end # Used for keying off JavaScript. diff --git a/app/javascript/application.js b/app/javascript/application.js index 8f2ff3a980..b37af4a96b 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -49,6 +49,10 @@ toastr.options = { "timeOut": "1400" } +// This global variable tracks whether Litepicker is actively managing the date range input field. +// It prevents custom validation logic from interfering when Litepicker is in use. +window.isLitepickerActive = false; + function isMobileResolution() { return $(window).width() < 992; } @@ -58,57 +62,94 @@ function isShortHeightScreen() { } $(document).ready(function(){ - const hash = window.location.hash; - if (hash) { - $('ul.nav a[href="' + hash + '"]').tab('show'); - } - const isMobile = isMobileResolution(); - const isShortHeight = isShortHeightScreen(); - - const calendarElement = document.getElementById('calendar'); - if (calendarElement) { - new Calendar(calendarElement, { - timeZone: 'UTC', - firstDay: 1, - plugins: [luxonPlugin, dayGridPlugin, listPlugin], - displayEventTime: true, - eventLimit: true, - events: 'schedule.json', - height: isMobile || isShortHeight ? 'auto' : 'parent', - defaultView: isMobile ? 'listWeek' : 'month' - }).render(); - } - - const rangeElement = document.getElementById("filters_date_range"); - if (!rangeElement) { - return; - } - const today = DateTime.now(); - const startDate = new Date(rangeElement.dataset["initialStartDate"]); - const endDate = new Date(rangeElement.dataset["initialEndDate"]); - - const picker = new Litepicker({ - element: rangeElement, - plugins: ['ranges'], - startDate: startDate, - endDate: endDate, - format: "MMMM D, YYYY", - ranges: { - customRanges: { - 'Default': [today.minus({'months': 2}).toJSDate(), today.plus({'months': 1}).toJSDate()], - 'All Time': [today.minus({ 'years': 100 }).toJSDate(), today.plus({ 'years': 1 }).toJSDate()], - 'Today': [today.toJSDate(), today.toJSDate()], - 'Yesterday': [today.minus({'days': 1}).toJSDate(), today.minus({'days': 1}).toJSDate()], - 'Last 7 Days': [today.minus({'days': 6}).toJSDate(), today.toJSDate()], - 'Last 30 Days': [today.minus({'days': 29}).toJSDate(), today.toJSDate()], - 'This Month': [today.startOf('month').toJSDate(), today.endOf('month').toJSDate()], - 'Last Month': [today.minus({'months': 1}).startOf('month').toJSDate(), - today.minus({'month': 1}).endOf('month').toJSDate()], - 'Last 12 Months': [today.minus({'months': 12}).plus({'days': 1}).toJSDate(), today.toJSDate()], - 'Prior Year': [today.startOf('year').minus({'years': 1}).toJSDate(), today.minus({'year': 1}).endOf('year').toJSDate()], - 'This Year': [today.startOf('year').toJSDate(), today.endOf('year').toJSDate()], - } - } - }); - picker.setDateRange(startDate, endDate); + const hash = window.location.hash; + if (hash) { + $('ul.nav a[href="' + hash + '"]').tab("show"); + } + const isMobile = isMobileResolution(); + const isShortHeight = isShortHeightScreen(); + + const calendarElement = document.getElementById("calendar"); + if (calendarElement) { + new Calendar(calendarElement, { + timeZone: "UTC", + firstDay: 1, + plugins: [luxonPlugin, dayGridPlugin, listPlugin], + displayEventTime: true, + eventLimit: true, + events: "schedule.json", + height: isMobile || isShortHeight ? "auto" : "parent", + defaultView: isMobile ? "listWeek" : "month", + }).render(); + } + + const rangeElement = document.getElementById("filters_date_range"); + if (!rangeElement) { + return; + } + const today = DateTime.now(); + const startDate = new Date(rangeElement.dataset["initialStartDate"]); + const endDate = new Date(rangeElement.dataset["initialEndDate"]); + + const picker = new Litepicker({ + element: rangeElement, + plugins: ["ranges"], + startDate: startDate, + endDate: endDate, + format: "MMMM D, YYYY", + ranges: { + customRanges: { + Default: [ + today.minus({ months: 2 }).toJSDate(), + today.plus({ months: 1 }).toJSDate(), + ], + "All Time": [ + today.minus({ years: 100 }).toJSDate(), + today.plus({ years: 1 }).toJSDate(), + ], + Today: [today.toJSDate(), today.toJSDate()], + Yesterday: [ + today.minus({ days: 1 }).toJSDate(), + today.minus({ days: 1 }).toJSDate(), + ], + "Last 7 Days": [today.minus({ days: 6 }).toJSDate(), today.toJSDate()], + "Last 30 Days": [ + today.minus({ days: 29 }).toJSDate(), + today.toJSDate(), + ], + "This Month": [ + today.startOf("month").toJSDate(), + today.endOf("month").toJSDate(), + ], + "Last Month": [ + today.minus({ months: 1 }).startOf("month").toJSDate(), + today.minus({ month: 1 }).endOf("month").toJSDate(), + ], + "Last 12 Months": [ + today.minus({ months: 12 }).plus({ days: 1 }).toJSDate(), + today.toJSDate(), + ], + "Prior Year": [ + today.startOf("year").minus({ years: 1 }).toJSDate(), + today.minus({ year: 1 }).endOf("year").toJSDate(), + ], + "This Year": [ + today.startOf("year").toJSDate(), + today.endOf("year").toJSDate(), + ], + }, + }, + }); + + // litepicker docs aren't clear on how to register events + // https://github.com/wakirin/Litepicker/issues/301 + picker.on("show", () => { + window.isLitepickerActive = true; + }); + + picker.on("hide", () => { + window.isLitepickerActive = false; + }); + + picker.setDateRange(startDate, endDate); }); diff --git a/app/javascript/controllers/date_range_controller.js b/app/javascript/controllers/date_range_controller.js new file mode 100644 index 0000000000..a8c31aeb0b --- /dev/null +++ b/app/javascript/controllers/date_range_controller.js @@ -0,0 +1,46 @@ +// This Stimulus controller is used to handle custom validation for the date range input field. +// Litepicker.js manages the date range field and prevents invalid data when users interact with its calendar control. +// However, if a user tabs into the field and enters invalid data without triggering Litepicker events, +// Litepicker won't validate the input, leaving invalid data in the field. +// This controller ensures that in such cases, custom validation is performed to alert the user about invalid input. + +import { Controller } from "@hotwired/stimulus"; +import { DateTime } from "luxon"; + +export default class extends Controller { + static targets = ["input"]; + + connect() { + this.initialStart = this.inputTarget.dataset.initialStartDate; + this.initialEnd = this.inputTarget.dataset.initialEndDate; + this.format = "MMMM d, yyyy"; + } + + validate(event) { + event.preventDefault(); + + if (window.isLitepickerActive) { + return; + } + + const value = this.inputTarget.value.trim(); + const [startStr, endStr] = value.split(" - ").map((s) => s.trim()); + + const isValid = this.isValidDateRange(startStr, endStr); + + if (!isValid) { + alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") + } + } + + isValidDateRange(startStr, endStr) { + try { + const start = DateTime.fromFormat(startStr, this.format); + const end = DateTime.fromFormat(endStr, this.format); + + return start.isValid && end.isValid && start <= end; + } catch (error) { + return false; + } + } +} diff --git a/app/views/shared/_date_range_picker.html.erb b/app/views/shared/_date_range_picker.html.erb index 0ff5302330..cd20b551e5 100644 --- a/app/views/shared/_date_range_picker.html.erb +++ b/app/views/shared/_date_range_picker.html.erb @@ -5,6 +5,9 @@ placeholder: "January 1, 2011 - December 31, 2011", class: "#{css_class}", autocomplete: "on", data: { + controller: "date-range", + date_range_target: "input", + action: "blur->date-range#validate", initial_start_date: @selected_date_interval.first.strftime("%B %d, %Y"), initial_end_date: @selected_date_interval.last.strftime("%B %d, %Y"), } %> diff --git a/spec/helpers/date_range_helper_spec.rb b/spec/helpers/date_range_helper_spec.rb new file mode 100644 index 0000000000..73b91007bf --- /dev/null +++ b/spec/helpers/date_range_helper_spec.rb @@ -0,0 +1,48 @@ +require "rails_helper" + +RSpec.describe DateRangeHelper do + let(:dummy_class) do + Class.new do + include DateRangeHelper + attr_accessor :params, :flash + + def initialize(params = {}, flash = nil) + @params = params + @flash = flash + end + end + end + + describe "#selected_interval" do + context "with a valid date range" do + it "parses the dates correctly" do + valid_range = "February 21, 2025 - May 22, 2025" + flash_double = double("flash", now: {}) + helper = dummy_class.new({filters: {date_range: valid_range}}, flash_double) + + interval = helper.selected_interval + + expect(interval).to eq([ + Date.new(2025, 2, 21), + Date.new(2025, 5, 22) + ]) + expect(helper.flash.now[:notice]).to be_nil + end + end + + context "with an invalid date range" do + it "falls back to default date range and sets a flash notice" do + invalid_range = "November 08 - February 08" + flash_now = {} + flash_double = double("flash", now: flash_now) + helper = dummy_class.new({filters: {date_range: invalid_range}}, flash_double) + + interval = helper.selected_interval + default_start, default_end = helper.default_date.split(" - ").map { |d| Date.strptime(d, "%B %d, %Y") } + + expect(interval).to eq([default_start, default_end]) + expect(flash_now[:notice]).to eq("Invalid Date range provided. Reset to default date range") + end + end + end +end diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 3c7ba1213d..aaaf3fb769 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -83,4 +83,62 @@ def date_range_picker_select_range(range_name) expect(page).to have_css("table tbody tr", count: 1) end end + + context "when entering an invalid date range" do + before do + sign_out user + travel_to Time.zone.local(2019, 7, 31) + sign_in user + end + + # This test is designed to simulate the case where a user tabs into the date range input field, types in an invalid value, + # and then presses Enter to submit the form. In the real application: + # - When the user tabs into the field, the Litepicker.js events (which manage the date range input) don't get triggered. + # - As a result, invalid data can be sent to the server without the client-side validation taking place. + # + # In contrast, if the user clicks on the input field, Litepicker.js would register, validate the input, and reset the + # value to a default range, preventing invalid data from being submitted. + # + # The goal of this test is to ensure that server-side validation works when invalid data is submitted, as it would happen + # when the user tabs into the input, enters invalid data, and submits the form. + # + # However, Capybara's standard methods like `fill_in` or `native.send_keys` trigger the Litepicker.js events, which + # prevent us from testing this edge case. These methods would cause Litepicker.js to validate the input, reset the + # value, and prevent invalid data from being submitted to the server. + # + # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input + # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the + # server-side validation without interference from the JavaScript events. + it "shows a flash notice and filters results as default" do + visit subject + + date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').focus();") + page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") + + expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") + expect(page).to have_css("table tbody tr", count: 4) + end + + # This test is similar to the above but simulates user clicking away from the date range field + # after having tabbed into it to type something invalid. In this case client side validation + # via a JavaScript alert should be triggered. + it "shows a JavaScript alert when user blurs" do + visit subject + + date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').focus();") + page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + + accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do + find('body').click + end + + valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" + fill_in "filters_date_range", with: valid_date_range + find(:id, 'filters_date_range').native.send_keys(:enter) + expect(page).to have_css("table tbody tr", count: 1) + end + end end From f8ccea2ba0367ecd7e003521e2ed29a45ebaa287 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sat, 17 May 2025 07:03:27 -0400 Subject: [PATCH 02/18] temporarily remove invalid date range system tests to investigate CI failures --- .../date_range_picker_shared_example.rb | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index aaaf3fb769..398e9ca4b3 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -84,61 +84,61 @@ def date_range_picker_select_range(range_name) end end - context "when entering an invalid date range" do - before do - sign_out user - travel_to Time.zone.local(2019, 7, 31) - sign_in user - end - - # This test is designed to simulate the case where a user tabs into the date range input field, types in an invalid value, - # and then presses Enter to submit the form. In the real application: - # - When the user tabs into the field, the Litepicker.js events (which manage the date range input) don't get triggered. - # - As a result, invalid data can be sent to the server without the client-side validation taking place. - # - # In contrast, if the user clicks on the input field, Litepicker.js would register, validate the input, and reset the - # value to a default range, preventing invalid data from being submitted. - # - # The goal of this test is to ensure that server-side validation works when invalid data is submitted, as it would happen - # when the user tabs into the input, enters invalid data, and submits the form. - # - # However, Capybara's standard methods like `fill_in` or `native.send_keys` trigger the Litepicker.js events, which - # prevent us from testing this edge case. These methods would cause Litepicker.js to validate the input, reset the - # value, and prevent invalid data from being submitted to the server. - # - # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input - # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the - # server-side validation without interference from the JavaScript events. - it "shows a flash notice and filters results as default" do - visit subject - - date_range = "nov 08 - feb 08" - page.execute_script("document.getElementById('filters_date_range').focus();") - page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") - - expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") - expect(page).to have_css("table tbody tr", count: 4) - end - - # This test is similar to the above but simulates user clicking away from the date range field - # after having tabbed into it to type something invalid. In this case client side validation - # via a JavaScript alert should be triggered. - it "shows a JavaScript alert when user blurs" do - visit subject - - date_range = "nov 08 - feb 08" - page.execute_script("document.getElementById('filters_date_range').focus();") - page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - - accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do - find('body').click - end - - valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" - fill_in "filters_date_range", with: valid_date_range - find(:id, 'filters_date_range').native.send_keys(:enter) - expect(page).to have_css("table tbody tr", count: 1) - end - end + # context "when entering an invalid date range" do + # before do + # sign_out user + # travel_to Time.zone.local(2019, 7, 31) + # sign_in user + # end + + # This test is designed to simulate the case where a user tabs into the date range input field, types in an invalid value, + # and then presses Enter to submit the form. In the real application: + # - When the user tabs into the field, the Litepicker.js events (which manage the date range input) don't get triggered. + # - As a result, invalid data can be sent to the server without the client-side validation taking place. + # + # In contrast, if the user clicks on the input field, Litepicker.js would register, validate the input, and reset the + # value to a default range, preventing invalid data from being submitted. + # + # The goal of this test is to ensure that server-side validation works when invalid data is submitted, as it would happen + # when the user tabs into the input, enters invalid data, and submits the form. + # + # However, Capybara's standard methods like `fill_in` or `native.send_keys` trigger the Litepicker.js events, which + # prevent us from testing this edge case. These methods would cause Litepicker.js to validate the input, reset the + # value, and prevent invalid data from being submitted to the server. + # + # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input + # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the + # server-side validation without interference from the JavaScript events. + # it "shows a flash notice and filters results as default" do + # visit subject + + # date_range = "nov 08 - feb 08" + # page.execute_script("document.getElementById('filters_date_range').focus();") + # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + # page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") + + # expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") + # expect(page).to have_css("table tbody tr", count: 4) + # end + + # This test is similar to the above but simulates user clicking away from the date range field + # after having tabbed into it to type something invalid. In this case client side validation + # via a JavaScript alert should be triggered. + # it "shows a JavaScript alert when user blurs" do + # visit subject + + # date_range = "nov 08 - feb 08" + # page.execute_script("document.getElementById('filters_date_range').focus();") + # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + + # accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do + # find('body').click + # end + + # valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" + # fill_in "filters_date_range", with: valid_date_range + # find(:id, 'filters_date_range').native.send_keys(:enter) + # expect(page).to have_css("table tbody tr", count: 1) + # end + # end end From ad51bd76b9d827974c2917e07b7ff914dfc7ac45 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sat, 17 May 2025 07:15:37 -0400 Subject: [PATCH 03/18] Revert "temporarily remove invalid date range system tests to investigate CI failures" This reverts commit f8ccea2ba0367ecd7e003521e2ed29a45ebaa287. --- .../date_range_picker_shared_example.rb | 114 +++++++++--------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 398e9ca4b3..aaaf3fb769 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -84,61 +84,61 @@ def date_range_picker_select_range(range_name) end end - # context "when entering an invalid date range" do - # before do - # sign_out user - # travel_to Time.zone.local(2019, 7, 31) - # sign_in user - # end - - # This test is designed to simulate the case where a user tabs into the date range input field, types in an invalid value, - # and then presses Enter to submit the form. In the real application: - # - When the user tabs into the field, the Litepicker.js events (which manage the date range input) don't get triggered. - # - As a result, invalid data can be sent to the server without the client-side validation taking place. - # - # In contrast, if the user clicks on the input field, Litepicker.js would register, validate the input, and reset the - # value to a default range, preventing invalid data from being submitted. - # - # The goal of this test is to ensure that server-side validation works when invalid data is submitted, as it would happen - # when the user tabs into the input, enters invalid data, and submits the form. - # - # However, Capybara's standard methods like `fill_in` or `native.send_keys` trigger the Litepicker.js events, which - # prevent us from testing this edge case. These methods would cause Litepicker.js to validate the input, reset the - # value, and prevent invalid data from being submitted to the server. - # - # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input - # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the - # server-side validation without interference from the JavaScript events. - # it "shows a flash notice and filters results as default" do - # visit subject - - # date_range = "nov 08 - feb 08" - # page.execute_script("document.getElementById('filters_date_range').focus();") - # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - # page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") - - # expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") - # expect(page).to have_css("table tbody tr", count: 4) - # end - - # This test is similar to the above but simulates user clicking away from the date range field - # after having tabbed into it to type something invalid. In this case client side validation - # via a JavaScript alert should be triggered. - # it "shows a JavaScript alert when user blurs" do - # visit subject - - # date_range = "nov 08 - feb 08" - # page.execute_script("document.getElementById('filters_date_range').focus();") - # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - - # accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do - # find('body').click - # end - - # valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" - # fill_in "filters_date_range", with: valid_date_range - # find(:id, 'filters_date_range').native.send_keys(:enter) - # expect(page).to have_css("table tbody tr", count: 1) - # end - # end + context "when entering an invalid date range" do + before do + sign_out user + travel_to Time.zone.local(2019, 7, 31) + sign_in user + end + + # This test is designed to simulate the case where a user tabs into the date range input field, types in an invalid value, + # and then presses Enter to submit the form. In the real application: + # - When the user tabs into the field, the Litepicker.js events (which manage the date range input) don't get triggered. + # - As a result, invalid data can be sent to the server without the client-side validation taking place. + # + # In contrast, if the user clicks on the input field, Litepicker.js would register, validate the input, and reset the + # value to a default range, preventing invalid data from being submitted. + # + # The goal of this test is to ensure that server-side validation works when invalid data is submitted, as it would happen + # when the user tabs into the input, enters invalid data, and submits the form. + # + # However, Capybara's standard methods like `fill_in` or `native.send_keys` trigger the Litepicker.js events, which + # prevent us from testing this edge case. These methods would cause Litepicker.js to validate the input, reset the + # value, and prevent invalid data from being submitted to the server. + # + # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input + # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the + # server-side validation without interference from the JavaScript events. + it "shows a flash notice and filters results as default" do + visit subject + + date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').focus();") + page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") + + expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") + expect(page).to have_css("table tbody tr", count: 4) + end + + # This test is similar to the above but simulates user clicking away from the date range field + # after having tabbed into it to type something invalid. In this case client side validation + # via a JavaScript alert should be triggered. + it "shows a JavaScript alert when user blurs" do + visit subject + + date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').focus();") + page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + + accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do + find('body').click + end + + valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" + fill_in "filters_date_range", with: valid_date_range + find(:id, 'filters_date_range').native.send_keys(:enter) + expect(page).to have_css("table tbody tr", count: 1) + end + end end From aaf82ef97ed4f50cb328bdd3503f106dc4c0c136 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 06:24:11 -0400 Subject: [PATCH 04/18] temp changes to investigate js timing failure for system tests on ci --- spec/support/date_range_picker_shared_example.rb | 10 ++++++++-- spec/system/distribution_system_spec.rb | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index aaaf3fb769..6412876099 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -117,8 +117,14 @@ def date_range_picker_select_range(range_name) page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") - expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") - expect(page).to have_css("table tbody tr", count: 4) + # Temp: investigating timing failure on CI + puts "🔍 URL: #{page.current_url}" + # puts "📄 Page body preview:\n#{page.body[0..1000]}" + puts "📄 Page text: #{page.text}" + # expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") + # expect(page).to have_css("table tbody tr", count: 4) + expect(page).to have_selector(".alert.notice", text: "Invalid Date range provided. Reset to default date range", wait: 30) + # expect(page).to have_css("table tbody tr", count: 4, wait: 10) end # This test is similar to the above but simulates user clicking away from the date range field diff --git a/spec/system/distribution_system_spec.rb b/spec/system/distribution_system_spec.rb index 11bd930ccd..3c3d8e9cb3 100644 --- a/spec/system/distribution_system_spec.rb +++ b/spec/system/distribution_system_spec.rb @@ -739,7 +739,7 @@ end end - context "when filtering on the index page" do + fcontext "when filtering on the index page" do subject { distributions_path } let(:item_category) { create(:item_category) } let(:item1) { create(:item, name: "Good item", item_category: item_category, organization: organization) } From f0194207eeeb37450ba2200c56b88fea5aeafcb7 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 06:33:00 -0400 Subject: [PATCH 05/18] remove attempt to focus tests for ci failure investigation --- spec/system/distribution_system_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/distribution_system_spec.rb b/spec/system/distribution_system_spec.rb index 3c3d8e9cb3..11bd930ccd 100644 --- a/spec/system/distribution_system_spec.rb +++ b/spec/system/distribution_system_spec.rb @@ -739,7 +739,7 @@ end end - fcontext "when filtering on the index page" do + context "when filtering on the index page" do subject { distributions_path } let(:item_category) { create(:item_category) } let(:item1) { create(:item, name: "Good item", item_category: item_category, organization: organization) } From 3e5a8b046d807d565cb1549139d35580887b90fd Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 06:36:05 -0400 Subject: [PATCH 06/18] temp ci failure investigation - run only distribution system tests which include invalid date range testing --- .github/workflows/rspec-system.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index 2402b60d87..5115ab207b 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -79,7 +79,9 @@ jobs: KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }} KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_LOG_LEVEL: info - KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" + # KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/distribution_system_spec.rb" + run: | RUBYOPT='-W:no-deprecated -W:no-experimental' bin/knapsack_pro_rspec - name: Upload artifacts From 23beb1a2646303022cf28701278ddaa52798476f Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 07:13:55 -0400 Subject: [PATCH 07/18] temp investigation for ci failure - try to get to failure quickly --- .github/workflows/rspec-system.yml | 3 ++- spec/system/investigation_system_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 spec/system/investigation_system_spec.rb diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index 5115ab207b..9c01f24169 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -80,7 +80,8 @@ jobs: KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_LOG_LEVEL: info # KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" - KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/distribution_system_spec.rb" + # KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/distribution_system_spec.rb" + KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/investigation_system_spec.rb" run: | RUBYOPT='-W:no-deprecated -W:no-experimental' bin/knapsack_pro_rspec diff --git a/spec/system/investigation_system_spec.rb b/spec/system/investigation_system_spec.rb new file mode 100644 index 0000000000..b5282cff5f --- /dev/null +++ b/spec/system/investigation_system_spec.rb @@ -0,0 +1,23 @@ +RSpec.feature "Distributions", type: :system do + let(:organization) { create(:organization) } + let(:user) { create(:user, organization: organization) } + let(:storage_location) { create(:storage_location, organization: organization, name: "Test Storage Location") } + let(:organization_admin) { create(:organization_admin, organization: organization) } + let!(:partner) { create(:partner, organization: organization, name: "Test Partner") } + + before do + sign_in(user) + setup_storage_location(storage_location) + end + + context "when filtering on the index page" do + subject { distributions_path } + let(:item_category) { create(:item_category) } + let(:item1) { create(:item, name: "Good item", item_category: item_category, organization: organization) } + let(:item2) { create(:item, name: "Crap item", organization: organization) } + let(:partner1) { create(:partner, name: "This Guy", email: "thisguy@example.com", organization: organization) } + let(:partner2) { create(:partner, name: "Not This Guy", email: "ntg@example.com", organization: organization) } + + it_behaves_like "Date Range Picker", Distribution, :issued_at + end +end From 540c6cd21223af7afcca70f4274f23b3b67a9deb Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 07:31:12 -0400 Subject: [PATCH 08/18] investigate ci failure - attempt to submit form without js having a chance to simulate invalid data getting to server --- .../date_range_picker_shared_example.rb | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 6412876099..fe89e413e0 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -115,15 +115,29 @@ def date_range_picker_select_range(range_name) date_range = "nov 08 - feb 08" page.execute_script("document.getElementById('filters_date_range').focus();") page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") - - # Temp: investigating timing failure on CI + # What we really want here is to simulate user hitting Enter to submit the form but only this click worked + # But on CI, this ends up triggering a JS alert instead of submitting the form + # FIXME: Can we use something like requestSubmit() to submit the form? + # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit + # page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") + page.execute_script(<<~JS) + var form = document.getElementById('filters_date_range').closest('form'); + form.requestSubmit(); + JS + + # Temp: investigating timing failure on CI and URL has not transitioned yet, it remains on: + # URL: http://127.0.0.1:35713/distributions + # Whereas locally it's transitioned to reflect the filters: + # http://127.0.0.1:53534/distributions?filters%5Bby_item_id%5D=&filters%5Bby_partner%5D=&filters%5Bby_location%5D=&filters%5Bby_state%5D=&filters%5Bdate_range%5D=nov+08+-+feb+08&filters%5Bdate_range_label%5D=during+the+period+31+May+to+31+Aug&button= + # So on CI, clicking the filter button after "tabbing" into the date range field does not submit the form, or not yet, and JS alert is running first puts "🔍 URL: #{page.current_url}" - # puts "📄 Page body preview:\n#{page.body[0..1000]}" + + # === ON CI: JS ALERT SHOWS UP HERE === + puts "📄 Page text: #{page.text}" - # expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") - # expect(page).to have_css("table tbody tr", count: 4) - expect(page).to have_selector(".alert.notice", text: "Invalid Date range provided. Reset to default date range", wait: 30) + expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") + expect(page).to have_css("table tbody tr", count: 4) + # expect(page).to have_selector(".alert.notice", text: "Invalid Date range provided. Reset to default date range", wait: 30) # expect(page).to have_css("table tbody tr", count: 4, wait: 10) end From f7faba91ba297c8643c77cd3ed598b236c05678d Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 07:35:14 -0400 Subject: [PATCH 09/18] temp ci investigation - focus only on server side validation test --- .../date_range_picker_shared_example.rb | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index fe89e413e0..2629a1fd55 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -141,24 +141,25 @@ def date_range_picker_select_range(range_name) # expect(page).to have_css("table tbody tr", count: 4, wait: 10) end + # Temp remove while investigating CI failure on server-side validation test above # This test is similar to the above but simulates user clicking away from the date range field # after having tabbed into it to type something invalid. In this case client side validation # via a JavaScript alert should be triggered. - it "shows a JavaScript alert when user blurs" do - visit subject - - date_range = "nov 08 - feb 08" - page.execute_script("document.getElementById('filters_date_range').focus();") - page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - - accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do - find('body').click - end - - valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" - fill_in "filters_date_range", with: valid_date_range - find(:id, 'filters_date_range').native.send_keys(:enter) - expect(page).to have_css("table tbody tr", count: 1) - end + # it "shows a JavaScript alert when user blurs" do + # visit subject + + # date_range = "nov 08 - feb 08" + # page.execute_script("document.getElementById('filters_date_range').focus();") + # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + + # accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do + # find('body').click + # end + + # valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" + # fill_in "filters_date_range", with: valid_date_range + # find(:id, 'filters_date_range').native.send_keys(:enter) + # expect(page).to have_css("table tbody tr", count: 1) + # end end end From d5780911ef2a4cda12fb52f774ef7fa149515adc Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 07:43:19 -0400 Subject: [PATCH 10/18] more ci failure investigation - how far does it get before js alert popups up --- spec/support/date_range_picker_shared_example.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 2629a1fd55..8381898474 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -114,7 +114,9 @@ def date_range_picker_select_range(range_name) date_range = "nov 08 - feb 08" page.execute_script("document.getElementById('filters_date_range').focus();") + puts "🔍 AFTER FOCUS - URL: #{page.current_url}" page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + puts "🔍 AFTER POPULATE DATE RANGE - URL: #{page.current_url}" # What we really want here is to simulate user hitting Enter to submit the form but only this click worked # But on CI, this ends up triggering a JS alert instead of submitting the form # FIXME: Can we use something like requestSubmit() to submit the form? @@ -130,7 +132,7 @@ def date_range_picker_select_range(range_name) # Whereas locally it's transitioned to reflect the filters: # http://127.0.0.1:53534/distributions?filters%5Bby_item_id%5D=&filters%5Bby_partner%5D=&filters%5Bby_location%5D=&filters%5Bby_state%5D=&filters%5Bdate_range%5D=nov+08+-+feb+08&filters%5Bdate_range_label%5D=during+the+period+31+May+to+31+Aug&button= # So on CI, clicking the filter button after "tabbing" into the date range field does not submit the form, or not yet, and JS alert is running first - puts "🔍 URL: #{page.current_url}" + puts "🔍 AFTER SUBMIT FORM - URL: #{page.current_url}" # === ON CI: JS ALERT SHOWS UP HERE === From 2b237f11fd709447d81afe47c1e7e6940391c302 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 09:22:09 -0400 Subject: [PATCH 11/18] experiment to temp disable custom js validation in test to simulate server side validation --- app/javascript/controllers/date_range_controller.js | 5 +++++ spec/support/date_range_picker_shared_example.rb | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/javascript/controllers/date_range_controller.js b/app/javascript/controllers/date_range_controller.js index a8c31aeb0b..d2ddf63ba8 100644 --- a/app/javascript/controllers/date_range_controller.js +++ b/app/javascript/controllers/date_range_controller.js @@ -19,6 +19,11 @@ export default class extends Controller { validate(event) { event.preventDefault(); + // experiment + if (this.inputTarget.dataset.skipValidation === "true") { + return; + } + if (window.isLitepickerActive) { return; } diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 8381898474..a44dfdbe1d 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -112,6 +112,9 @@ def date_range_picker_select_range(range_name) it "shows a flash notice and filters results as default" do visit subject + # experiment + page.execute_script("document.getElementById('filters_date_range').dataset.skipValidation = 'true';") + date_range = "nov 08 - feb 08" page.execute_script("document.getElementById('filters_date_range').focus();") puts "🔍 AFTER FOCUS - URL: #{page.current_url}" @@ -122,6 +125,7 @@ def date_range_picker_select_range(range_name) # FIXME: Can we use something like requestSubmit() to submit the form? # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit # page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") + # === ON CI: JS ALERT SHOWS UP HERE? === page.execute_script(<<~JS) var form = document.getElementById('filters_date_range').closest('form'); form.requestSubmit(); @@ -134,7 +138,7 @@ def date_range_picker_select_range(range_name) # So on CI, clicking the filter button after "tabbing" into the date range field does not submit the form, or not yet, and JS alert is running first puts "🔍 AFTER SUBMIT FORM - URL: #{page.current_url}" - # === ON CI: JS ALERT SHOWS UP HERE === + # === ON CI: JS ALERT SHOWS UP HERE OR EVEN EARLIER === puts "📄 Page text: #{page.text}" expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") From 47ab1190149b144aa6e086d75c31e24b8addd1a6 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 09:26:48 -0400 Subject: [PATCH 12/18] now that server side validation test passes on ci, try also with client side validation test --- .../date_range_picker_shared_example.rb | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index a44dfdbe1d..4475efffc0 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -151,21 +151,23 @@ def date_range_picker_select_range(range_name) # This test is similar to the above but simulates user clicking away from the date range field # after having tabbed into it to type something invalid. In this case client side validation # via a JavaScript alert should be triggered. - # it "shows a JavaScript alert when user blurs" do - # visit subject - - # date_range = "nov 08 - feb 08" - # page.execute_script("document.getElementById('filters_date_range').focus();") - # page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - - # accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do - # find('body').click - # end - - # valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" - # fill_in "filters_date_range", with: valid_date_range - # find(:id, 'filters_date_range').native.send_keys(:enter) - # expect(page).to have_css("table tbody tr", count: 1) - # end + it "shows a JavaScript alert when user blurs" do + visit subject + + date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').focus();") + puts "🔍 JS BLUR TEST - AFTER FOCUS - URL: #{page.current_url}" + page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") + puts "🔍 JS BLUR TEST - AFTER ENTER VALUE - URL: #{page.current_url}" + + accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do + find('body').click + end + + valid_date_range = "#{Time.zone.local(2019, 7, 22).to_fs(:date_picker)} - #{Time.zone.local(2019, 7, 28).to_fs(:date_picker)}" + fill_in "filters_date_range", with: valid_date_range + find(:id, 'filters_date_range').native.send_keys(:enter) + expect(page).to have_css("table tbody tr", count: 1) + end end end From cea3a0d2e9d5afde73a4028401bb536f5098a025 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 11:22:34 -0400 Subject: [PATCH 13/18] try with all tests --- .github/workflows/rspec-system.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index 9c01f24169..700717c7ed 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -79,9 +79,9 @@ jobs: KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }} KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_LOG_LEVEL: info - # KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" + KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" # KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/distribution_system_spec.rb" - KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/investigation_system_spec.rb" + # KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/investigation_system_spec.rb" run: | RUBYOPT='-W:no-deprecated -W:no-experimental' bin/knapsack_pro_rspec From 4f25aff6bb4f73704616da590457930ce666f435 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 12:58:47 -0400 Subject: [PATCH 14/18] cleanup CI test failure investigation code --- .github/workflows/rspec-system.yml | 2 -- .../controllers/date_range_controller.js | 1 - .../date_range_picker_shared_example.rb | 27 +------------------ spec/system/investigation_system_spec.rb | 23 ---------------- 4 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 spec/system/investigation_system_spec.rb diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index 700717c7ed..d4b9b4fd33 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -80,8 +80,6 @@ jobs: KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_LOG_LEVEL: info KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" - # KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/distribution_system_spec.rb" - # KNAPSACK_PRO_TEST_FILE_PATTERN: "spec/system/investigation_system_spec.rb" run: | RUBYOPT='-W:no-deprecated -W:no-experimental' bin/knapsack_pro_rspec diff --git a/app/javascript/controllers/date_range_controller.js b/app/javascript/controllers/date_range_controller.js index d2ddf63ba8..2b112a2728 100644 --- a/app/javascript/controllers/date_range_controller.js +++ b/app/javascript/controllers/date_range_controller.js @@ -19,7 +19,6 @@ export default class extends Controller { validate(event) { event.preventDefault(); - // experiment if (this.inputTarget.dataset.skipValidation === "true") { return; } diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 4475efffc0..52f3b795e6 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -112,42 +112,19 @@ def date_range_picker_select_range(range_name) it "shows a flash notice and filters results as default" do visit subject - # experiment - page.execute_script("document.getElementById('filters_date_range').dataset.skipValidation = 'true';") - date_range = "nov 08 - feb 08" + page.execute_script("document.getElementById('filters_date_range').dataset.skipValidation = 'true';") page.execute_script("document.getElementById('filters_date_range').focus();") - puts "🔍 AFTER FOCUS - URL: #{page.current_url}" page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - puts "🔍 AFTER POPULATE DATE RANGE - URL: #{page.current_url}" - # What we really want here is to simulate user hitting Enter to submit the form but only this click worked - # But on CI, this ends up triggering a JS alert instead of submitting the form - # FIXME: Can we use something like requestSubmit() to submit the form? - # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit - # page.execute_script("document.querySelector('[data-test-id=\"filter-button\"]').click();") - # === ON CI: JS ALERT SHOWS UP HERE? === page.execute_script(<<~JS) var form = document.getElementById('filters_date_range').closest('form'); form.requestSubmit(); JS - # Temp: investigating timing failure on CI and URL has not transitioned yet, it remains on: - # URL: http://127.0.0.1:35713/distributions - # Whereas locally it's transitioned to reflect the filters: - # http://127.0.0.1:53534/distributions?filters%5Bby_item_id%5D=&filters%5Bby_partner%5D=&filters%5Bby_location%5D=&filters%5Bby_state%5D=&filters%5Bdate_range%5D=nov+08+-+feb+08&filters%5Bdate_range_label%5D=during+the+period+31+May+to+31+Aug&button= - # So on CI, clicking the filter button after "tabbing" into the date range field does not submit the form, or not yet, and JS alert is running first - puts "🔍 AFTER SUBMIT FORM - URL: #{page.current_url}" - - # === ON CI: JS ALERT SHOWS UP HERE OR EVEN EARLIER === - - puts "📄 Page text: #{page.text}" expect(page).to have_css(".alert.notice", text: "Invalid Date range provided. Reset to default date range") expect(page).to have_css("table tbody tr", count: 4) - # expect(page).to have_selector(".alert.notice", text: "Invalid Date range provided. Reset to default date range", wait: 30) - # expect(page).to have_css("table tbody tr", count: 4, wait: 10) end - # Temp remove while investigating CI failure on server-side validation test above # This test is similar to the above but simulates user clicking away from the date range field # after having tabbed into it to type something invalid. In this case client side validation # via a JavaScript alert should be triggered. @@ -156,9 +133,7 @@ def date_range_picker_select_range(range_name) date_range = "nov 08 - feb 08" page.execute_script("document.getElementById('filters_date_range').focus();") - puts "🔍 JS BLUR TEST - AFTER FOCUS - URL: #{page.current_url}" page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") - puts "🔍 JS BLUR TEST - AFTER ENTER VALUE - URL: #{page.current_url}" accept_alert("Please enter a valid date range (e.g., January 1, 2024 - March 15, 2024).") do find('body').click diff --git a/spec/system/investigation_system_spec.rb b/spec/system/investigation_system_spec.rb deleted file mode 100644 index b5282cff5f..0000000000 --- a/spec/system/investigation_system_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -RSpec.feature "Distributions", type: :system do - let(:organization) { create(:organization) } - let(:user) { create(:user, organization: organization) } - let(:storage_location) { create(:storage_location, organization: organization, name: "Test Storage Location") } - let(:organization_admin) { create(:organization_admin, organization: organization) } - let!(:partner) { create(:partner, organization: organization, name: "Test Partner") } - - before do - sign_in(user) - setup_storage_location(storage_location) - end - - context "when filtering on the index page" do - subject { distributions_path } - let(:item_category) { create(:item_category) } - let(:item1) { create(:item, name: "Good item", item_category: item_category, organization: organization) } - let(:item2) { create(:item, name: "Crap item", organization: organization) } - let(:partner1) { create(:partner, name: "This Guy", email: "thisguy@example.com", organization: organization) } - let(:partner2) { create(:partner, name: "Not This Guy", email: "ntg@example.com", organization: organization) } - - it_behaves_like "Date Range Picker", Distribution, :issued_at - end -end From aebd4461f198399a44b6d36537b93022742516f4 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 14:35:22 -0400 Subject: [PATCH 15/18] remove unused data test id from filter button --- app/helpers/ui_helper.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/helpers/ui_helper.rb b/app/helpers/ui_helper.rb index 9ed262e849..50ce491377 100644 --- a/app/helpers/ui_helper.rb +++ b/app/helpers/ui_helper.rb @@ -110,11 +110,8 @@ def edit_button_to(link, options = {}, properties = {}) _link_to link, { icon: "pencil-square-o", type: "primary", text: "Edit", size: "xs" }.merge(options), properties end - def filter_button(options = {}, data = {}) - _button_to( - { icon: "filter", type: "primary", text: "Filter", size: "md" }.merge(options), - { data: { test_id: "filter-button" }.merge(data) } - ) + def filter_button(options = {}) + _button_to({ icon: "filter", type: "primary", text: "Filter", size: "md" }.merge(options)) end # Used for keying off JavaScript. From 4847e74f658968b003e71fb70288d6832b5663a5 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 14:52:17 -0400 Subject: [PATCH 16/18] document disable validation option for client side JS --- app/javascript/controllers/date_range_controller.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/javascript/controllers/date_range_controller.js b/app/javascript/controllers/date_range_controller.js index 2b112a2728..ba41c062b6 100644 --- a/app/javascript/controllers/date_range_controller.js +++ b/app/javascript/controllers/date_range_controller.js @@ -3,6 +3,13 @@ // However, if a user tabs into the field and enters invalid data without triggering Litepicker events, // Litepicker won't validate the input, leaving invalid data in the field. // This controller ensures that in such cases, custom validation is performed to alert the user about invalid input. +// +// Note: The `data-skip-validation` attribute is used only in automated system tests to disable client-side validation. +// In real user interactions, if a user enters an invalid date and immediately hits Enter, the form submits before +// JS blur-based validation runs, so server-side validation is exercised as expected. +// However, in system tests (especially on CI), the JS blur validation always runs before form submission, +// making it impossible to test server-side validation for this scenario unless client-side validation is disabled. +// This attribute should only be set in test code. import { Controller } from "@hotwired/stimulus"; import { DateTime } from "luxon"; @@ -19,11 +26,7 @@ export default class extends Controller { validate(event) { event.preventDefault(); - if (this.inputTarget.dataset.skipValidation === "true") { - return; - } - - if (window.isLitepickerActive) { + if (this.inputTarget.dataset.skipValidation === "true" || window.isLitepickerActive) { return; } From c4f127379409f8ae93a8116b7e57a9f879660352 Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 14:57:48 -0400 Subject: [PATCH 17/18] consolidate all js execution in test --- spec/support/date_range_picker_shared_example.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/support/date_range_picker_shared_example.rb b/spec/support/date_range_picker_shared_example.rb index 52f3b795e6..e2b477e407 100644 --- a/spec/support/date_range_picker_shared_example.rb +++ b/spec/support/date_range_picker_shared_example.rb @@ -107,17 +107,17 @@ def date_range_picker_select_range(range_name) # value, and prevent invalid data from being submitted to the server. # # To properly test this case, we use `execute_script` to simulate typing the invalid date directly into the input - # field, bypassing the Litepicker.js events entirely. This allows us to submit the invalid data and test the - # server-side validation without interference from the JavaScript events. + # field, and submitting the form, bypassing the Litepicker.js events entirely. it "shows a flash notice and filters results as default" do visit subject date_range = "nov 08 - feb 08" - page.execute_script("document.getElementById('filters_date_range').dataset.skipValidation = 'true';") - page.execute_script("document.getElementById('filters_date_range').focus();") - page.execute_script("document.getElementById('filters_date_range').value = '#{date_range}';") page.execute_script(<<~JS) - var form = document.getElementById('filters_date_range').closest('form'); + var input = document.getElementById('filters_date_range'); + input.dataset.skipValidation = 'true'; + input.focus(); + input.value = '#{date_range}'; + var form = input.closest('form'); form.requestSubmit(); JS From ac155a686581fc4d641d24492791787343495aeb Mon Sep 17 00:00:00 2001 From: Daniela Baron Date: Sun, 18 May 2025 15:08:17 -0400 Subject: [PATCH 18/18] undo changes to rspec system workflow file --- .github/workflows/rspec-system.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rspec-system.yml b/.github/workflows/rspec-system.yml index d4b9b4fd33..2402b60d87 100644 --- a/.github/workflows/rspec-system.yml +++ b/.github/workflows/rspec-system.yml @@ -80,7 +80,6 @@ jobs: KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_LOG_LEVEL: info KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/**{,/*/**}/*_spec.rb,spec/requests/**{,/*/**}/*_spec.rb}" - run: | RUBYOPT='-W:no-deprecated -W:no-experimental' bin/knapsack_pro_rspec - name: Upload artifacts