From 3f44835ee12a161a8ea21bdba37439454fa456b6 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Sat, 11 Apr 2026 18:19:13 +0200 Subject: [PATCH] fix(test): freeze time in tests to prevent flaky failures Remove timecop gem dependency and use Rails' travel_to helper. Time-based scopes compare against Time.zone.now evaluated at query time, while test fixtures used relative times evaluated at creation time. This caused flaky tests when any time passed between fixture creation and query. Files changed: - Remove timecop from Gemfile and test.rb config - Add travel_to blocks to time-dependent tests in shared examples, listable_spec, attendance_warning_spec, feature tests, rake tasks, and presenter specs Fixes flaky test failures in CI runs. --- Gemfile | 1 - Gemfile.lock | 2 - config/environments/test.rb | 12 +-- spec/features/admin/announcements_spec.rb | 22 ++--- spec/features/chapter_spec.rb | 86 +++++++++++-------- spec/features/listing_coaches_spec.rb | 44 +++++----- spec/features/listing_events_spec.rb | 48 +++++++---- spec/lib/tasks/feedback_rake_spec.rb | 58 +++++++------ spec/lib/tasks/reminders_meeting_rake_spec.rb | 31 ++++--- .../lib/tasks/reminders_workshop_rake_spec.rb | 29 ++++--- spec/models/attendance_warning_spec.rb | 8 +- spec/models/concerns/listable_spec.rb | 46 +++++----- spec/presenters/chapter_presenter_spec.rb | 10 ++- spec/presenters/workshop_presenter_spec.rb | 51 +++++------ .../behaves_like_an_invitation.rb | 50 ++++++----- 15 files changed, 278 insertions(+), 220 deletions(-) diff --git a/Gemfile b/Gemfile index e08d7ba56..df0b0daaa 100644 --- a/Gemfile +++ b/Gemfile @@ -114,7 +114,6 @@ group :test do gem 'shoulda-matchers', '~> 7.0' gem 'simplecov', require: false gem 'simplecov-lcov', require: false - gem 'timecop', '~> 0.9.10' gem 'webmock' end diff --git a/Gemfile.lock b/Gemfile.lock index 18e6291c9..2d3d8e494 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -573,7 +573,6 @@ GEM execjs (>= 0.3.0, < 3) thor (1.5.0) tilt (2.7.0) - timecop (0.9.10) timeout (0.6.0) tsort (0.2.0) turbo-rails (2.0.23) @@ -700,7 +699,6 @@ DEPENDENCIES stimulus-rails stripe terser - timecop (~> 0.9.10) turbo-rails tzinfo-data view_component diff --git a/config/environments/test.rb b/config/environments/test.rb index 7b18fa6e0..bcf11a2e7 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,5 +1,4 @@ -require "active_support/core_ext/integer/time" -require "timecop" +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -16,10 +15,10 @@ # this is usually not necessary, and can slow down your test suite. However, it's # recommended that you enable it in continuous integration systems to ensure eager # loading is working properly before deploying your code. - config.eager_load = ENV["CI"].present? + config.eager_load = ENV['CI'].present? # Configure public file server for tests with cache-control for performance. - config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.hour.to_i}" } + config.public_file_server.headers = { 'cache-control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports. config.consider_all_requests_local = true @@ -41,7 +40,7 @@ config.action_mailer.delivery_method = :test # Set host to be used by links generated in mailer templates. - config.action_mailer.default_url_options = { host: "localhost:3000" } + config.action_mailer.default_url_options = { host: 'localhost:3000' } # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr @@ -63,7 +62,4 @@ Bullet.bullet_logger = true Bullet.raise = false # raise an error if n+1 query occurs end - - # https://github.com/travisjeffery/timecop#timecopsafe_mode - Timecop.safe_mode = true end diff --git a/spec/features/admin/announcements_spec.rb b/spec/features/admin/announcements_spec.rb index 4491dfdfc..800039879 100644 --- a/spec/features/admin/announcements_spec.rb +++ b/spec/features/admin/announcements_spec.rb @@ -15,7 +15,7 @@ click_on 'announcement[create]' expect(page).to have_content('Announcement successfully created') - expect(page.current_path).to eq(admin_announcements_path) + expect(page).to have_current_path(admin_announcements_path, ignore_query: true) end end @@ -37,18 +37,20 @@ expect(page).to have_content('Announcement successfully updated') expect(page).to have_content('New event coming up soon! Stay tuned.') - expect(page.current_path).to eq(admin_announcements_path) + expect(page).to have_current_path(admin_announcements_path, ignore_query: true) end end scenario 'can view all announcements' do - announcement = Fabricate(:announcement) - old_announcement = Fabricate(:announcement, expires_at: Time.zone.now - 1.week) + travel_to(Time.current) do + announcement = Fabricate(:announcement) + old_announcement = Fabricate(:announcement, expires_at: 1.week.ago) - visit admin_announcements_path + visit admin_announcements_path - expect(page).to have_content(announcement.message) - expect(page).to have_content(old_announcement.message) + expect(page).to have_content(announcement.message) + expect(page).to have_content(old_announcement.message) + end end scenario 'can successfully send a new announcement to every group' do @@ -60,7 +62,7 @@ expect(page).to have_content('An announcement to every group') expect(page).to have_content("Coaches #{chapter.name}") expect(page).to have_content("Students #{chapter.name}") - expect(page.current_path).to eq(admin_announcements_path) + expect(page).to have_current_path(admin_announcements_path, ignore_query: true) end scenario 'can successfully send a new announcement to selected groups' do @@ -71,8 +73,8 @@ expect(page).to have_content('An announcement to selected groups') expect(page).to have_content("Coaches #{chapter.name}") - expect(page).to_not have_content("Students #{chapter.name}") - expect(page.current_path).to eq(admin_announcements_path) + expect(page).not_to have_content("Students #{chapter.name}") + expect(page).to have_current_path(admin_announcements_path, ignore_query: true) end end end diff --git a/spec/features/chapter_spec.rb b/spec/features/chapter_spec.rb index a22dd9cac..087c83935 100644 --- a/spec/features/chapter_spec.rb +++ b/spec/features/chapter_spec.rb @@ -9,11 +9,13 @@ end it 'a visitor to the website can access inactive chapter events' do - past_workshop = Fabricate(:workshop, chapter: inactive_chapter, date_and_time: Time.zone.today - 2.weeks) + travel_to(Time.current) do + past_workshop = Fabricate(:workshop, chapter: inactive_chapter, date_and_time: 2.weeks.ago) - visit workshop_path(past_workshop) + visit workshop_path(past_workshop) - expect(page).to have_content "Workshop at #{past_workshop.host.name}" + expect(page).to have_content "Workshop at #{past_workshop.host.name}" + end end end @@ -25,59 +27,67 @@ end it 'renders chapter without organisers' do - chapter = Fabricate(:chapter_without_organisers, name: "Empty Chapter") + chapter = Fabricate(:chapter_without_organisers, name: 'Empty Chapter') expect(chapter.organisers.size).to eq 0 visit chapter_path(chapter.slug) - expect(page).to have_content "Empty Chapter" - expect(page).not_to have_content "Team" + expect(page).to have_content 'Empty Chapter' + expect(page).not_to have_content 'Team' end it 'renders any upcoming workshops for the chapter' do - chapter = Fabricate(:chapter) - workshops = 2.times.map do |n| - Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.now + 9.days - n.weeks) - end - - visit chapter_path(chapter.slug) - workshops.each do |workshop| - expect(page).to have_content "Workshop at #{workshop.host.name}" + travel_to(Time.current) do + chapter = Fabricate(:chapter) + workshops = 2.times.map do |n| + Fabricate(:workshop, chapter: chapter, date_and_time: 9.days.from_now - n.weeks) + end + + visit chapter_path(chapter.slug) + workshops.each do |workshop| + expect(page).to have_content "Workshop at #{workshop.host.name}" + end end end it 'renders any upcoming events for the chapter' do - chapter = Fabricate(:chapter) - 2.times.map do |n| - Fabricate(:event, name: "Event #{n + 1}", - chapters: [chapter], - date_and_time: Time.zone.now + 2.months - n.months) + travel_to(Time.current) do + chapter = Fabricate(:chapter) + 2.times.map do |n| + Fabricate(:event, name: "Event #{n + 1}", + chapters: [chapter], + date_and_time: 2.months.from_now - n.months) + end + + visit chapter_path(chapter.slug) + expect(page).to have_content 'Event 1' + expect(page).to have_content 'Event 2' end - - visit chapter_path(chapter.slug) - expect(page).to have_content 'Event 1' - expect(page).to have_content 'Event 2' end it 'renders the most recent past workshop for the chapter' do - chapter = Fabricate(:chapter) - past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.today - 2.weeks) - recent_past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.today - 1.week) - - visit chapter_path(chapter.slug) - expect(page).to have_content "Workshop at #{recent_past_workshop.host.name}" - expect(page).to_not have_content "Workshop at #{past_workshop.host.name}" + travel_to(Time.current) do + chapter = Fabricate(:chapter) + past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: 2.weeks.ago) + recent_past_workshop = Fabricate(:workshop, chapter: chapter, date_and_time: 1.week.ago) + + visit chapter_path(chapter.slug) + expect(page).to have_content "Workshop at #{recent_past_workshop.host.name}" + expect(page).not_to have_content "Workshop at #{past_workshop.host.name}" + end end it 'renders the 6 most recent sponsors for the chapter' do - chapter = Fabricate(:chapter) - workshops = 2.times.map do |n| - Fabricate(:workshop, chapter: chapter, date_and_time: Time.zone.now - n.weeks) - end - - visit chapter_path(chapter.slug) - workshops.each do |workshop| - expect(page).to have_link(workshop.sponsors.name) + travel_to(Time.current) do + chapter = Fabricate(:chapter) + workshops = 2.times.map do |n| + Fabricate(:workshop, chapter: chapter, date_and_time: n.weeks.ago) + end + + visit chapter_path(chapter.slug) + workshops.each do |workshop| + expect(page).to have_link(workshop.sponsors.name) + end end end end diff --git a/spec/features/listing_coaches_spec.rb b/spec/features/listing_coaches_spec.rb index b40cbe356..716c089d8 100644 --- a/spec/features/listing_coaches_spec.rb +++ b/spec/features/listing_coaches_spec.rb @@ -6,33 +6,37 @@ end scenario 'I can see the top coaches by year' do - latest_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 1.year) - old_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 3.years) - invitations = 2.times { Fabricate(:attended_coach, workshop: latest_workshop) } - older_invitations = 4.times { Fabricate(:attended_coach, workshop: old_workshop) } + travel_to(Time.current) do + latest_workshop = Fabricate(:workshop, date_and_time: 1.year.ago) + old_workshop = Fabricate(:workshop, date_and_time: 3.years.ago) + 2.times { Fabricate(:attended_coach, workshop: latest_workshop) } + 4.times { Fabricate(:attended_coach, workshop: old_workshop) } - visit coaches_path(year: latest_workshop.date_and_time.year) - expect(page).to have_css(".coach", count: 2) + visit coaches_path(year: latest_workshop.date_and_time.year) + expect(page).to have_css('.coach', count: 2) - visit coaches_path(year: old_workshop.date_and_time.year) - expect(page).to have_css(".coach", count: 4) + visit coaches_path(year: old_workshop.date_and_time.year) + expect(page).to have_css('.coach', count: 4) + end end scenario 'I can navigate the top coaches by year' do - current_workshop = Fabricate(:workshop, date_and_time: Time.zone.now) - latest_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 1.year) - old_workshop = Fabricate(:workshop, date_and_time: Time.zone.now - 3.years) - current_invitations = 1.times { Fabricate(:attended_coach, workshop: current_workshop) } - invitations = 3.times { Fabricate(:attended_coach, workshop: latest_workshop) } - older_invitations = 2.times { Fabricate(:attended_coach, workshop: old_workshop) } + travel_to(Time.current) do + current_workshop = Fabricate(:workshop, date_and_time: Time.current) + latest_workshop = Fabricate(:workshop, date_and_time: 1.year.ago) + old_workshop = Fabricate(:workshop, date_and_time: 3.years.ago) + 1.times { Fabricate(:attended_coach, workshop: current_workshop) } + 3.times { Fabricate(:attended_coach, workshop: latest_workshop) } + 2.times { Fabricate(:attended_coach, workshop: old_workshop) } - visit coaches_path - expect(page).to have_css(".coach", count: 1) + visit coaches_path + expect(page).to have_css('.coach', count: 1) - click_on latest_workshop.date_and_time.year.to_s - expect(page).to have_css(".coach", count: 3) + click_on latest_workshop.date_and_time.year.to_s + expect(page).to have_css('.coach', count: 3) - click_on old_workshop.date_and_time.year.to_s - expect(page).to have_css(".coach", count: 2) + click_on old_workshop.date_and_time.year.to_s + expect(page).to have_css('.coach', count: 2) + end end end diff --git a/spec/features/listing_events_spec.rb b/spec/features/listing_events_spec.rb index 03d33cfe9..05e4636ae 100644 --- a/spec/features/listing_events_spec.rb +++ b/spec/features/listing_events_spec.rb @@ -5,21 +5,25 @@ let!(:event) { Fabricate(:event) } scenario 'displays upcoming events page' do - visit upcoming_events_path - expect(page).to have_content 'Upcoming Events' - expect(page).to have_content event.name + travel_to(Time.current) do + visit upcoming_events_path + expect(page).to have_content 'Upcoming Events' + expect(page).to have_content event.name + end end end describe 'I can see past events' do let!(:chapter) { Fabricate(:chapter, active: true) } - let!(:past_event) { Fabricate(:event, date_and_time: Time.zone.now - 2.weeks) } - let!(:past_workshop) { Fabricate(:workshop, date_and_time: Time.zone.now - 1.week, chapter: chapter) } + let!(:past_event) { Fabricate(:event, date_and_time: 2.weeks.ago) } + let!(:past_workshop) { Fabricate(:workshop, date_and_time: 1.week.ago, chapter: chapter) } scenario 'displays past events page' do - visit past_events_path - expect(page).to have_content 'Past Events' - expect(page).to have_content past_event.name + travel_to(Time.current) do + visit past_events_path + expect(page).to have_content 'Past Events' + expect(page).to have_content past_event.name + end end end @@ -33,26 +37,32 @@ context 'pagination' do scenario 'past events paginates at 20 per page' do - chapter = Fabricate(:chapter, active: true) - Fabricate.times(22, :event, date_and_time: 2.weeks.ago) - Fabricate(:workshop, date_and_time: 3.weeks.ago, chapter: chapter) + travel_to(Time.current) do + chapter = Fabricate(:chapter, active: true) + Fabricate.times(22, :event, date_and_time: 2.weeks.ago) + Fabricate(:workshop, date_and_time: 3.weeks.ago, chapter: chapter) - visit past_events_path - expect(page).to have_selector('.card', count: 20) + visit past_events_path + expect(page).to have_selector('.card', count: 20) + end end scenario 'past meetings paginate at 20 per page' do - Fabricate.times(22, :meeting, date_and_time: 2.weeks.ago) + travel_to(Time.current) do + Fabricate.times(22, :meeting, date_and_time: 2.weeks.ago) - visit past_events_path - expect(page).to have_selector('.card', count: 20) + visit past_events_path + expect(page).to have_selector('.card', count: 20) + end end scenario 'upcoming meetings paginate at 20 per page' do - Fabricate.times(22, :meeting, date_and_time: 2.weeks.from_now) + travel_to(Time.current) do + Fabricate.times(22, :meeting, date_and_time: 2.weeks.from_now) - visit upcoming_events_path - expect(page).to have_selector('.card', count: 20) + visit upcoming_events_path + expect(page).to have_selector('.card', count: 20) + end end end end diff --git a/spec/lib/tasks/feedback_rake_spec.rb b/spec/lib/tasks/feedback_rake_spec.rb index 8a23ec3d7..de8d0c065 100644 --- a/spec/lib/tasks/feedback_rake_spec.rb +++ b/spec/lib/tasks/feedback_rake_spec.rb @@ -1,45 +1,53 @@ RSpec.describe 'rake feedback:request', type: :task do context 'when most recent workshop has attendances' do - let(:workshop) { Fabricate(:workshop, date_and_time: 23.hours.ago) } - let(:student) { Fabricate(:member) } - - it "preloads the Rails environment" do - expect(task.prerequisites).to include "environment" + it 'preloads the Rails environment' do + expect(task.prerequisites).to include 'environment' end - it 'should gracefully run' do - expect { task.invoke }.to_not raise_error + it 'gracefullies run' do + travel_to(Time.current) do + Fabricate(:workshop, date_and_time: 23.hours.ago) + expect { task.invoke }.not_to raise_error + end end it 'generates a FeedbackRequest' do - Fabricate(:attending_workshop_invitation, role: 'Student', member: student, workshop: workshop) + travel_to(Time.current) do + workshop = Fabricate(:workshop, date_and_time: 23.hours.ago) + student = Fabricate(:member) + Fabricate(:attending_workshop_invitation, role: 'Student', member: student, workshop: workshop) - expect(Workshop).to receive(:completed_since_yesterday).and_return([workshop]) - expect(FeedbackRequest).to receive(:create).with(member: student, workshop: workshop, submited: false) + expect(Workshop).to receive(:completed_since_yesterday).and_return([workshop]) + expect(FeedbackRequest).to receive(:create).with(member: student, workshop: workshop, submited: false) - task.execute + task.execute + end end it 'only generates a FeedbackRequest for workshops that took place in the last 24 hours' do - past_workshops = [Fabricate(:workshop, date_and_time: 3.days.ago), - Fabricate(:workshop, date_and_time: 24.hours.ago)] + travel_to(Time.current) do + student = Fabricate(:member) - yesterdays_workshops = [Fabricate(:workshop, date_and_time: 1.hour.ago), - Fabricate(:workshop, date_and_time: 20.hours.ago), - Fabricate(:workshop, date_and_time: (23.hours + 59.minutes).ago), - Fabricate(:virtual_workshop, date_and_time: 23.hours.ago)] + past_workshops = [Fabricate(:workshop, date_and_time: 3.days.ago), + Fabricate(:workshop, date_and_time: 24.hours.ago)] - past_workshops.each { |w| Fabricate(:attending_workshop_invitation, member: student, workshop: w) } - yesterdays_workshops.each { |w| Fabricate(:attending_workshop_invitation, member: student, workshop: w) } + yesterdays_workshops = [Fabricate(:workshop, date_and_time: 1.hour.ago), + Fabricate(:workshop, date_and_time: 20.hours.ago), + Fabricate(:workshop, date_and_time: (23.hours + 59.minutes).ago), + Fabricate(:virtual_workshop, date_and_time: 23.hours.ago)] - task.execute + past_workshops.each { |w| Fabricate(:attending_workshop_invitation, member: student, workshop: w) } + yesterdays_workshops.each { |w| Fabricate(:attending_workshop_invitation, member: student, workshop: w) } - past_workshops.each do |workshop| - expect(FeedbackRequest.where(member: student, workshop: workshop, submited: false).exists?).to eq(false) - end + task.execute + + past_workshops.each do |workshop| + expect(FeedbackRequest.where(member: student, workshop: workshop, submited: false).exists?).to eq(false) + end - yesterdays_workshops.each do |workshop| - expect(FeedbackRequest.where(member: student, workshop: workshop, submited: false).exists?).to eq(true) + yesterdays_workshops.each do |workshop| + expect(FeedbackRequest.where(member: student, workshop: workshop, submited: false).exists?).to eq(true) + end end end end diff --git a/spec/lib/tasks/reminders_meeting_rake_spec.rb b/spec/lib/tasks/reminders_meeting_rake_spec.rb index 19cb2422f..f890603de 100644 --- a/spec/lib/tasks/reminders_meeting_rake_spec.rb +++ b/spec/lib/tasks/reminders_meeting_rake_spec.rb @@ -1,23 +1,28 @@ RSpec.describe 'rake reminders:meeting', type: :task do - it "preloads the Rails environment" do - expect(task.prerequisites).to include "environment" + it 'preloads the Rails environment' do + expect(task.prerequisites).to include 'environment' end - it 'should gracefully run' do - expect { task.invoke }.to_not raise_error + it 'gracefullies run' do + travel_to(Time.current) do + Fabricate(:meeting, date_and_time: 29.hours.from_now) + expect { task.invoke }.not_to raise_error + end end it 'sends out reminders for meetings taking place between 6 and 30 hours from now' do - meeting = Fabricate(:meeting, date_and_time: Time.zone.now + 29.hours) - just_now_meeting = Fabricate(:meeting, date_and_time: Time.zone.now + 2.hours) - past_meeting = Fabricate(:meeting, date_and_time: 1.day.ago) + travel_to(Time.current) do + meeting = Fabricate(:meeting, date_and_time: 29.hours.from_now) + just_now_meeting = Fabricate(:meeting, date_and_time: 2.hours.from_now) + past_meeting = Fabricate(:meeting, date_and_time: 1.day.ago) - invitation_manager = InvitationManager.new - expect(InvitationManager).to receive(:new).and_return(invitation_manager) - expect(invitation_manager).to receive(:send_monthly_attendance_reminder_emails).with(meeting) - expect(invitation_manager).to_not receive(:send_monthly_attendance_reminder_emails).with(past_meeting) - expect(invitation_manager).to_not receive(:send_monthly_attendance_reminder_emails).with(just_now_meeting) + invitation_manager = InvitationManager.new + expect(InvitationManager).to receive(:new).and_return(invitation_manager) + expect(invitation_manager).to receive(:send_monthly_attendance_reminder_emails).with(meeting) + expect(invitation_manager).not_to receive(:send_monthly_attendance_reminder_emails).with(past_meeting) + expect(invitation_manager).not_to receive(:send_monthly_attendance_reminder_emails).with(just_now_meeting) - task.execute + task.execute + end end end diff --git a/spec/lib/tasks/reminders_workshop_rake_spec.rb b/spec/lib/tasks/reminders_workshop_rake_spec.rb index f54cabaa7..730acb546 100644 --- a/spec/lib/tasks/reminders_workshop_rake_spec.rb +++ b/spec/lib/tasks/reminders_workshop_rake_spec.rb @@ -1,22 +1,27 @@ RSpec.describe 'rake reminders:workshop', type: :task do - let!(:workshop) { Fabricate(:workshop, date_and_time: Time.zone.now + 29.hours) } - - it "preloads the Rails environment" do - expect(task.prerequisites).to include "environment" + it 'preloads the Rails environment' do + expect(task.prerequisites).to include 'environment' end - it 'should gracefully run' do - expect { task.invoke }.to_not raise_error + it 'gracefullies run' do + travel_to(Time.current) do + Fabricate(:workshop, date_and_time: 29.hours.from_now) + expect { task.invoke }.not_to raise_error + end end it 'sends out reminders' do - invitation_manager = InvitationManager.new - expect(InvitationManager).to receive(:new).and_return(invitation_manager) - expect(invitation_manager).to receive(:send_workshop_attendance_reminders).with(workshop) + travel_to(Time.current) do + workshop = Fabricate(:workshop, date_and_time: 29.hours.from_now) + + invitation_manager = InvitationManager.new + expect(InvitationManager).to receive(:new).and_return(invitation_manager) + expect(invitation_manager).to receive(:send_workshop_attendance_reminders).with(workshop) - expect(InvitationManager).to receive(:new).and_return(invitation_manager) - expect(invitation_manager).to receive(:send_workshop_waiting_list_reminders).with(workshop) + expect(InvitationManager).to receive(:new).and_return(invitation_manager) + expect(invitation_manager).to receive(:send_workshop_waiting_list_reminders).with(workshop) - task.execute + task.execute + end end end diff --git a/spec/models/attendance_warning_spec.rb b/spec/models/attendance_warning_spec.rb index 8f9785546..e3e4e6623 100644 --- a/spec/models/attendance_warning_spec.rb +++ b/spec/models/attendance_warning_spec.rb @@ -20,10 +20,12 @@ describe '.scopes' do describe 'last_six_months' do it 'returns all attendance warnings issues in the last six months' do - Fabricate(:attendance_warning, member: member, created_at: 7.months.ago) - warnings = Fabricate.times(2, :attendance_warning, member: member, created_at: 5.months.ago) + travel_to(Time.current) do + Fabricate(:attendance_warning, member: member, created_at: 7.months.ago) + warnings = Fabricate.times(2, :attendance_warning, member: member, created_at: 5.months.ago) - expect(member.attendance_warnings.last_six_months).to contain_exactly(*warnings) + expect(member.attendance_warnings.last_six_months).to match_array(warnings) + end end end end diff --git a/spec/models/concerns/listable_spec.rb b/spec/models/concerns/listable_spec.rb index 9040f6f31..da41b254f 100644 --- a/spec/models/concerns/listable_spec.rb +++ b/spec/models/concerns/listable_spec.rb @@ -4,7 +4,7 @@ context 'scopes' do describe '#today_and_upcoming' do it 'returns a list of all today and upcoming workshops' do - Timecop.travel(Time.now.utc) do + travel_to(Time.current) do Fabricate.times(2, :past_workshop) future_workshops = Fabricate.times(1, :workshop) @@ -28,7 +28,7 @@ describe '#upcoming' do it 'returns a list of all upcoming workshops' do - Timecop.travel(Time.now.utc) do + travel_to(Time.current) do Fabricate.times(2, :past_workshop) future_workshops = Fabricate.times(1, :workshop) @@ -48,7 +48,7 @@ describe '#past' do it 'returns a list of all upcoming workshops' do - Timecop.travel(Time.now.utc) do + travel_to(Time.current) do past_workshops = Fabricate.times(2, :past_workshop) Fabricate.times(1, :workshop) @@ -71,33 +71,39 @@ describe '#completed_since_yesterday' do it 'returns a list of yesterday\'s events' do - Fabricate(:workshop, date_and_time: 24.hours.ago) - Fabricate(:workshop, date_and_time: 25.hours.ago) - latest = [Fabricate(:workshop, date_and_time: 3.hours.ago), - Fabricate(:workshop, date_and_time: 12.hours.ago), - Fabricate(:workshop, date_and_time: 23.hours.ago)] - - expect(Workshop.completed_since_yesterday).to eq(latest) + travel_to(Time.current) do + Fabricate(:workshop, date_and_time: 24.hours.ago) + Fabricate(:workshop, date_and_time: 25.hours.ago) + latest = [Fabricate(:workshop, date_and_time: 3.hours.ago), + Fabricate(:workshop, date_and_time: 12.hours.ago), + Fabricate(:workshop, date_and_time: 23.hours.ago)] + + expect(Workshop.completed_since_yesterday).to eq(latest) + end end end end describe '#next' do it 'returns the next workshop to take place' do - next_workshop = Fabricate(:workshop, date_and_time: Time.zone.now + 24.hours) - Fabricate(:workshop, date_and_time: Time.zone.now + 29.hours) + travel_to(Time.current) do + next_workshop = Fabricate(:workshop, date_and_time: Time.current + 24.hours) + Fabricate(:workshop, date_and_time: Time.current + 29.hours) - expect(Workshop.next).to eq(next_workshop) + expect(Workshop.next).to eq(next_workshop) + end end it 'returns the latest workshop to have taken place' do - past_most_recent_workshop = Fabricate(:workshop, date_and_time: 2.hours.ago) - Fabricate(:workshop, date_and_time: 5.hours.ago) - Fabricate(:workshop, date_and_time: 2.days.ago) - - most_recent = Workshop.most_recent - expect(most_recent).to eq(past_most_recent_workshop) - expect(most_recent.sponsors).to eq(past_most_recent_workshop.sponsors) + travel_to(Time.current) do + past_most_recent_workshop = Fabricate(:workshop, date_and_time: 2.hours.ago) + Fabricate(:workshop, date_and_time: 5.hours.ago) + Fabricate(:workshop, date_and_time: 2.days.ago) + + most_recent = Workshop.most_recent + expect(most_recent).to eq(past_most_recent_workshop) + expect(most_recent.sponsors).to eq(past_most_recent_workshop.sponsors) + end end end end diff --git a/spec/presenters/chapter_presenter_spec.rb b/spec/presenters/chapter_presenter_spec.rb index 1924169ee..cdd3e3f51 100644 --- a/spec/presenters/chapter_presenter_spec.rb +++ b/spec/presenters/chapter_presenter_spec.rb @@ -3,11 +3,13 @@ let(:presenter) { ChapterPresenter.new(chapter) } it '#upcoming_workshops' do - Fabricate.times(2, :past_workshop, chapter: chapter) - workshops = Fabricate.times(3, :workshop, chapter: chapter, - date_and_time: Time.zone.now + 1.week) + travel_to(Time.current) do + Fabricate.times(2, :past_workshop, chapter: chapter) + workshops = Fabricate.times(3, :workshop, chapter: chapter, + date_and_time: 1.week.from_now) - expect(presenter.upcoming_workshops).to match_array(workshops) + expect(presenter.upcoming_workshops).to match_array(workshops) + end end it '#organisers' do diff --git a/spec/presenters/workshop_presenter_spec.rb b/spec/presenters/workshop_presenter_spec.rb index e1566082f..7e51353f5 100644 --- a/spec/presenters/workshop_presenter_spec.rb +++ b/spec/presenters/workshop_presenter_spec.rb @@ -10,7 +10,7 @@ def double_workshop(attending_coaches:, attending_students:) attending_students: double(:attending_students, count: attending_students)) end - context '#decorate' do + describe '#decorate' do it 'returns a workshop decorated with the WorkshopPresenter' do workshop = double(:workshop, virtual?: false) presenter = WorkshopPresenter.decorate(workshop) @@ -24,13 +24,13 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#address' do + describe '#address' do it 'returns the decorated address of the workshop\'s venue' do expect(presenter.address.to_html).to eq(AddressPresenter.new(host.address).to_html) end end - context '#attending_and_available_student_spots' do + describe '#attending_and_available_student_spots' do let(:workshop) { double_workshop(attending_coaches: 3, attending_students: 4) } it 'returns the attending students count over the available workshop spots' do @@ -38,7 +38,7 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#attending_and_available_coach_spots' do + describe '#attending_and_available_coach_spots' do let(:workshop) { double_workshop(attending_coaches: 3, attending_students: 4) } it 'returns the attending coaches count over the available workshop spots' do @@ -46,7 +46,7 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#title' do + describe '#title' do it 'returns the title of a workshop' do expect(presenter.title).to eq("Workshop at #{host.name}") end @@ -65,7 +65,7 @@ def double_workshop(attending_coaches:, attending_students:) workshop = Fabricate(:workshop, chapter: Fabricate(:chapter_without_organisers)) presenter = WorkshopPresenter.new(workshop) - expect(presenter.organisers).to match_array([]) + expect(presenter.organisers).to be_empty end it 'when there are organisers' do @@ -80,18 +80,18 @@ def double_workshop(attending_coaches:, attending_students:) end context 'time formatting' do - let(:workshop) { double(:workshop, date_and_time: Time.zone.now, ends_at: 1.hour.from_now) } + it '#start_time and #end_time' do + travel_to(Time.current) do + workshop = double(:workshop, date_and_time: Time.current, ends_at: 1.hour.from_now) + presenter = WorkshopPresenter.new(workshop) - it '#start_time' do - expect(presenter.start_time).to eq(I18n.l(workshop.date_and_time, format: :time)) - end - - it '#end_time' do - expect(presenter.end_time).to eq(I18n.l(workshop.ends_at, format: :time)) + expect(presenter.start_time).to eq(I18n.l(workshop.date_and_time, format: :time)) + expect(presenter.end_time).to eq(I18n.l(workshop.ends_at, format: :time)) + end end end - context '#attendees_csv' do + describe '#attendees_csv' do let(:invitations) do [Fabricate.times(2, :student_workshop_invitation), Fabricate.times(2, :coach_workshop_invitation)].flatten end @@ -111,14 +111,14 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#pairing_csv' do + describe '#pairing_csv' do let(:workshop) { double(:workshop, attendances: [invitation]) } let(:student) { Fabricate(:student) } let(:invitation) { Fabricate(:workshop_invitation, member: student, note: 'Note') } it 'returns a csv with all the details required to enable organisers to pair the participants' do student_pairing_array = [true, student.full_name, 'Student', invitation.tutorial, invitation.note, 'N/A'] - student_presenter = MemberPresenter.new(student) + MemberPresenter.new(student) expect(presenter.pairing_csv) .to eq(WorkshopPresenter::PAIRING_HEADINGS.join(',') + "\n" + @@ -131,14 +131,17 @@ def double_workshop(attending_coaches:, attending_students:) presenter = WorkshopPresenter.new(workshop) members = Fabricate.times(2, :member) members.each_with_index do |member, index| - index % 2 == 0 ? Fabricate(:attending_workshop_invitation, member: member, workshop: workshop) : - Fabricate(:attending_workshop_invitation, member: member, workshop: workshop, role: 'Coach') + if index.even? + Fabricate(:attending_workshop_invitation, member: member, workshop: workshop) + else + Fabricate(:attending_workshop_invitation, member: member, workshop: workshop, role: 'Coach') + end end expect(presenter.attendees_emails.split(', ')).to match_array(members.map(&:email)) end - context '#coach_spaces' do + describe '#coach_spaces' do it 'returns the available coach_spots' do expect(host).to receive(:coach_spots) @@ -146,7 +149,7 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#student_spaces' do + describe '#student_spaces' do it 'returns the available coach_spots' do expect(host).to receive(:seats) @@ -154,7 +157,7 @@ def double_workshop(attending_coaches:, attending_students:) end end - context '#spaces?' do + describe '#spaces?' do let(:sponsor) { double(:sponsor, coach_spots: 3, seats: 5, chapter: chapter) } def double_workshop(attending_coaches:, attending_students:) @@ -166,7 +169,7 @@ def double_workshop(attending_coaches:, attending_students:) context 'when the host has more available spots' do let(:workshop) { double_workshop(attending_coaches: 2, attending_students: 3) } - it 'it returns true' do + it 'returns true' do expect(presenter.spaces?).to eq(true) end end @@ -174,13 +177,13 @@ def double_workshop(attending_coaches:, attending_students:) context 'when the host has no more available spots' do let(:workshop) { double_workshop(attending_coaches: 3, attending_students: 5) } - it 'it returns false' do + it 'returns false' do expect(presenter.spaces?).to eq(false) end end end - context '#send_attending_email' do + describe '#send_attending_email' do it 'send an attending email to the invitation user' do workshop_invitation_mailer = double(:workshop_invitation_mailed, deliver_now: true) invitation = double(:invitation, member: double(:member)) diff --git a/spec/support/shared_examples/behaves_like_an_invitation.rb b/spec/support/shared_examples/behaves_like_an_invitation.rb index 1715f6c4c..9524f2544 100644 --- a/spec/support/shared_examples/behaves_like_an_invitation.rb +++ b/spec/support/shared_examples/behaves_like_an_invitation.rb @@ -3,11 +3,11 @@ let(:invitation) { Fabricate(invitation_type) } it 'has a token set on creation' do - expect(invitation.token).to_not be(nil) + expect(invitation.token).not_to be_nil end - context '#scopes' do - context '#not_accepted' do + describe '#scopes' do + describe '#not_accepted' do it 'selects when attended nil' do Fabricate(invitation_type, attending: nil) @@ -21,38 +21,46 @@ end it 'ignores when attended true' do - invitation = Fabricate(invitation_type, attending: true) + Fabricate(invitation_type, attending: true) expect(invitation_constant.not_accepted).to eq [] end end - let(:past_event) { Fabricate(event_type, date_and_time: 2.days.ago) } - let(:future_rsvp) { Fabricate(invitation_type, attending: true) } - let(:past_rsvp) { Fabricate(invitation_type, attending: true, event_type => past_event) } - let(:past_invitation) { Fabricate(invitation_type, event_type => past_event) } - - before(:each, data: true) do - future_rsvp - past_rsvp - past_invitation - end - - describe '#accepted', data: true do + describe '#accepted', :data do it 'returns a list of all rsvps' do - expect(invitation_constant.joins(event_type).accepted).to contain_exactly(future_rsvp, past_rsvp) + travel_to(Time.current) do + past_event = Fabricate(event_type, date_and_time: 2.days.ago) + future_rsvp = Fabricate(invitation_type, attending: true) + past_rsvp = Fabricate(invitation_type, attending: true, event_type => past_event) + + expect(invitation_constant.joins(event_type).accepted).to contain_exactly(future_rsvp, past_rsvp) + end end end - describe '#upcoming_rsvps', data: true do + describe '#upcoming_rsvps', :data do it 'returns a list of all upcoming rsvps' do - expect(invitation_constant.joins(event_type).upcoming_rsvps).to contain_exactly(future_rsvp) + travel_to(Time.current) do + past_event = Fabricate(event_type, date_and_time: 2.days.ago) + future_rsvp = Fabricate(invitation_type, attending: true) + Fabricate(invitation_type, attending: true, event_type => past_event) + + expect(invitation_constant.joins(event_type).upcoming_rsvps).to contain_exactly(future_rsvp) + end end end - describe '#taken_place', data: true do + describe '#taken_place', :data do it 'returns a list of all invitations for events that have already taken place' do - expect(invitation_constant.joins(event_type).taken_place).to contain_exactly(past_rsvp, past_invitation) + travel_to(Time.current) do + past_event = Fabricate(event_type, date_and_time: 2.days.ago) + Fabricate(invitation_type, attending: true) + past_rsvp = Fabricate(invitation_type, attending: true, event_type => past_event) + past_invitation = Fabricate(invitation_type, event_type => past_event) + + expect(invitation_constant.joins(event_type).taken_place).to contain_exactly(past_rsvp, past_invitation) + end end end end