Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve banner expiration validations and form #5828

Merged
merged 21 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jp524 marked this conversation as resolved.
Show resolved Hide resolved
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
jp524 marked this conversation as resolved.
Show resolved Hide resolved
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
jp524 marked this conversation as resolved.
Show resolved Hide resolved
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
41 changes: 32 additions & 9 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,22 +44,31 @@
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
banner = create(:banner, expires_at: "2024-06-13 12:00:00 UTC")
banner = create(:banner, expires_at: nil)
banner.update_columns(expires_at: "2024-06-13 12:00:00 UTC")
jp524 marked this conversation as resolved.
Show resolved Hide resolved

expires_at_in_pacific_time = banner.expires_at_in_time_zone("America/Los_Angeles")
expect(expires_at_in_pacific_time.to_s).to eq("2024-06-13 05:00:00 -0700")
Expand Down
76 changes: 72 additions & 4 deletions spec/requests/banners_spec.rb
jp524 marked this conversation as resolved.
Show resolved Hide resolved
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,71 @@
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
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
travel_to Time.new(2024, 6, 1, 11, 0, 0, "+03:00") do # 08:00 UTC
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to redirect_to banners_path
end
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
travel_to Time.new(2024, 6, 1, 11, 0, 0, "+03:00") do # 08:00 UTC
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
end

context "when client timezone is behind UTC" do
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
travel_to Time.new(2024, 6, 1, 11, 0, 0, "-04:00") do # 15:00 UTC
sign_in admin
post banners_path, params: {banner: banner_params}
expect(response).to redirect_to banners_path
end
end
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
travel_to Time.new(2024, 6, 1, 11, 0, 0, "-04:00") do # 15:00 UTC
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
jp524 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading