Skip to content

Commit

Permalink
Improve banner expiration validations and form (#5828)
Browse files Browse the repository at this point in the history
* Modify 'Expires At' column

* Do not display seconds in datetime field for Expires At

* Add server-side validation for Expires At

* Add client-side validation for Expires At

* Add server-side validation for Content

* Make expired banners inactive

* Lint code

* Modify banner system spec to use timezone

* Lint code

* Modify test to use 12hr time format

* Improve banner tests by removing travel

* Simplify banner system test of expiration date in the past

* Lint code

* Fix bug in banner Expires At validation

* Modify banner Expires At validation to work with time travelling tests

* Add requests tests for banner creation

* Replace cookies by helper in banner form

* Refactor banner request and model tests
  • Loading branch information
jp524 authored Jun 25, 2024
1 parent feafcec commit 2365e96
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 25 deletions.
2 changes: 1 addition & 1 deletion app/controllers/banners_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def set_banner
end

def banner_params
BannerParameters.new(params, current_user, cookies[:browser_time_zone])
BannerParameters.new(params, current_user, browser_time_zone)
end

def deactivate_alternate_active_banner
Expand Down
4 changes: 2 additions & 2 deletions app/helpers/banner_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ def conditionally_add_hidden_class(current_banner_is_active)

def banner_expiration_time_in_words(banner)
if banner.expired?
"Yes"
"Expired"
elsif banner.expires_at
"in #{distance_of_time_in_words(Time.now, banner.expires_at)}"
else
"No"
"No Expiration"
end
end
end
14 changes: 13 additions & 1 deletion app/models/banner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ class Banner < ApplicationRecord
scope :active, -> { where(active: true) }

validates_presence_of :name
validates_presence_of :content
validate :only_one_banner_is_active_per_organization
validate :expires_at_must_be_in_future

def expired?
expires_at && Time.current > expires_at
expired = expires_at && Time.current > expires_at
update(active: false) if active && expired
expired
end

# `expires_at` is stored in the database as UTC, but timezone information will be stripped before displaying on frontend
Expand All @@ -27,6 +31,14 @@ def only_one_banner_is_active_per_organization
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

# Validation using line below doesn't work with `travel_to` in specs. Must use detailed method
# validates_comparison_of :expires_at, greater_than: Time.current, message: "must take place in the future (after #{Time.current})", allow_blank: true
def expires_at_must_be_in_future
if expires_at.present? && expires_at < Time.current
errors.add(:expires_at, "must take place in the future (after #{Time.current})")
end
end
end

# == Schema Information
Expand Down
6 changes: 5 additions & 1 deletion app/views/banners/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
</div>
<span class="input-style-1">
<%= form.label :expires_at, "Expires at (optional)" %>
<%= form.datetime_field :expires_at, value: banner.expires_at_in_time_zone(cookies[:browser_time]), required: false %>
<%= form.datetime_field :expires_at,
value: banner.expires_at_in_time_zone(browser_time_zone),
required: false,
include_seconds: false,
min: Time.current.in_time_zone(browser_time_zone) %>
</span>
<div class="input-style-1">
<%= form.label :content %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/banners/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<tr>
<th>Name</th>
<th>Active?</th>
<th>Expires At</th>
<th>Expiration</th>
<th>Last Updated By</th>
<th>Updated At</th>
<th>Actions</th>
Expand Down
14 changes: 9 additions & 5 deletions spec/helpers/banner_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
context "when expires_at isn't set" do
let(:expires_at) { nil }

it "returns No" do
expect(helper.banner_expiration_time_in_words(banner)).to eq("No")
it "returns No Expiration" do
expect(helper.banner_expiration_time_in_words(banner)).to eq("No Expiration")
end
end

Expand All @@ -56,10 +56,14 @@
end

context "when expires_at is in the past" do
let(:expires_at) { 7.days.ago }
let(:expired_banner) do
banner = create(:banner, expires_at: nil)
banner.update_columns(expires_at: 2.days.ago)
banner
end

it "returns yes" do
expect(helper.banner_expiration_time_in_words(banner)).to eq("Yes")
it "returns Expired" do
expect(helper.banner_expiration_time_in_words(expired_banner)).to eq("Expired")
end
end
end
Expand Down
39 changes: 31 additions & 8 deletions spec/models/banner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,39 @@

RSpec.describe Banner, type: :model do
describe "#valid?" do
let(:casa_org) { create(:casa_org) }
let(:supervisor) { create(:supervisor, casa_org: casa_org) }

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

it "does not allow an expiry date set in the past" do
banner = build(:banner, casa_org: casa_org, user: supervisor, expires_at: 1.hour.ago)
expect(banner).to_not be_valid
end

it "allows an expiry date set in the future" do
banner = build(:banner, casa_org: casa_org, user: supervisor, expires_at: 1.day.from_now)
expect(banner).to be_valid
end

it "does not allow content to be empty" do
banner = build(:banner, casa_org: casa_org, user: supervisor, content: nil)
expect(banner).to_not be_valid
end
end

describe "#expired?" do
Expand All @@ -30,21 +44,30 @@
expect(banner).not_to be_expired
end

it "is false when expires_at is set but is after today" do
it "is false when expires_at is set but is in the future" do
banner = create(:banner, expires_at: 7.days.from_now)

expect(banner).not_to be_expired
end

it "is true when expires_at is set but is before today" do
banner = create(:banner, expires_at: 7.days.ago)

it "is true when expires_at is set but is in the past" do
banner = create(:banner, expires_at: nil)
banner.update_columns(expires_at: 1.hour.ago)
expect(banner).to be_expired
end

it "sets active to false when banner is expired" do
banner = create(:banner, expires_at: 1.hour.from_now)
expect(banner.active).to be true
travel 2.hours
banner.expired?
expect(banner.active).to be false
end
end

describe "#expires_at_in_time_zone" do
it "can shift time by timezone for equivalent times" do
travel_to Time.new(2024, 6, 1, 11, 0, 0, "UTC")
banner = create(:banner, expires_at: "2024-06-13 12:00:00 UTC")

expires_at_in_pacific_time = banner.expires_at_in_time_zone("America/Los_Angeles")
Expand Down
72 changes: 68 additions & 4 deletions spec/requests/banners_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@
end

context "when a banner has expires_at" do
let!(:active_banner) { create(:banner, casa_org: casa_org, expires_at: expires_at) }

context "when expires_at is after today" do
let(:expires_at) { 7.days.from_now }
let!(:active_banner) { create(:banner, casa_org: casa_org, expires_at: 7.days.from_now) }

it "displays the banner" do
sign_in volunteer
Expand All @@ -62,7 +60,10 @@
end

context "when expires_at is before today" do
let(:expires_at) { 7.days.ago }
let!(:active_banner) do
banner = create(:banner, casa_org: casa_org, expires_at: nil)
banner.update_columns(expires_at: 1.day.ago)
end

it "does not display the banner" do
sign_in volunteer
Expand All @@ -71,4 +72,67 @@
end
end
end

context "when creating a banner" do
let(:admin) { create(:casa_admin, casa_org: casa_org) }
let(:banner_params) do
{
user: admin,
active: false,
content: "Test",
name: "Test Announcement",
expires_at: expires_at
}
end

context "when client timezone is ahead of UTC" do
before { travel_to Time.new(2024, 6, 1, 11, 0, 0, "+03:00") } # 08:00 UTC

context "when submitted time is behind client but ahead of UTC" do
let(:expires_at) { Time.new(2024, 6, 1, 9, 0, 0, "UTC") } # 12:00 +03:00

it "succeeds" do
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to redirect_to banners_path
end
end

context "when submitted time is behind client and behind UTC" do
let(:expires_at) { Time.new(2024, 6, 1, 7, 0, 0, "UTC") } # 10:00 +03:00

it "fails" do
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to render_template "banners/new"
expect(response.body).to include "Expires at must take place in the future (after 2024-06-01 08:00:00 UTC)"
end
end
end

context "when client timezone is behind UTC" do
before { travel_to Time.new(2024, 6, 1, 11, 0, 0, "-04:00") } # 15:00 UTC

context "when submitted time is ahead of client and ahead of UTC" do
let(:expires_at) { Time.new(2024, 6, 1, 16, 0, 0, "UTC") } # 12:00 -04:00

it "succeeds" do
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to redirect_to banners_path
end
end

context "when submitted time is ahead of client but behind UTC" do
let(:expires_at) { Time.new(2024, 6, 1, 14, 0, 0, "UTC") } # 10:00 -04:00

it "fails" do
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to render_template "banners/new"
expect(response.body).to include "Expires at must take place in the future (after 2024-06-01 15:00:00 UTC)"
end
end
end
end
end
18 changes: 16 additions & 2 deletions spec/system/banners/new_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,28 @@
within "#banners" do
click_on "Edit", match: :first
end
fill_in "banner_expires_at", with: 7.days.ago.strftime("%m%d%Y\t%I%M%P")
fill_in "banner_expires_at", with: 2.days.from_now.strftime("%m%d%Y\t%I%M%P")
click_on "Submit"

visit banners_path
expect(page).to have_text("Expiring Announcement")

visit root_path
expect(page).not_to have_text("Please fill out this survey.")
expect(page).to have_text("Please fill out this survey.")
end

it "does not allow creation of banner with an expiration time set in the past" do
sign_in admin

visit banners_path
click_on "New Banner"
fill_in "Name", with: "Announcement not created"
fill_in "banner_expires_at", with: 1.week.ago.strftime("%m%d%Y\t%I%M%P")
fill_in_rich_text_area "banner_content", with: "Please fill out this survey."
click_on "Submit"

visit banners_path
expect(page).to_not have_text("Announcement not created")
end

describe "when an organization has an active banner" do
Expand Down
9 changes: 9 additions & 0 deletions spec/views/banners/new.html.erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:current_organization).and_return(current_organization)
without_partial_double_verification do
allow(view).to receive(:browser_time_zone).and_return("America/New_York")
end
allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)

assign :banners, [current_organization_banner]
Expand All @@ -30,6 +33,9 @@

allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:current_organization).and_return(current_organization)
without_partial_double_verification do
allow(view).to receive(:browser_time_zone).and_return("America/New_York")
end
allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)

assign :banners, [current_organization_banner]
Expand All @@ -51,6 +57,9 @@

allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:current_organization).and_return(current_organization)
without_partial_double_verification do
allow(view).to receive(:browser_time_zone).and_return("America/New_York")
end
allow(current_organization).to receive(:has_alternate_active_banner?).and_return(false)

assign :banners, []
Expand Down

0 comments on commit 2365e96

Please sign in to comment.