diff --git a/app/controllers/contest_descriptions_controller.rb b/app/controllers/contest_descriptions_controller.rb
index 52cff0af..beb67648 100644
--- a/app/controllers/contest_descriptions_controller.rb
+++ b/app/controllers/contest_descriptions_controller.rb
@@ -88,7 +88,10 @@ def handle_save(success, action)
format.turbo_stream {
render turbo_stream: turbo_stream.replace('contest_description_form', partial: 'contest_descriptions/form', locals: { contest_description: @contest_description }), status: :unprocessable_entity
}
- format.html { render action_name.to_sym, status: :unprocessable_entity }
+ format.html {
+ template = action_name == 'create' ? :new : :edit
+ render template, status: :unprocessable_entity
+ }
end
end
end
diff --git a/app/controllers/entry_rankings_controller.rb b/app/controllers/entry_rankings_controller.rb
index 2db9c370..42190650 100644
--- a/app/controllers/entry_rankings_controller.rb
+++ b/app/controllers/entry_rankings_controller.rb
@@ -43,10 +43,18 @@ def select_for_next_round
authorize @entry_ranking, :select_for_next_round?
selected = params[:selected_for_next_round] == '1'
- Rails.logger.debug { "Selected value before update: #{selected}" }
- if @entry_ranking.update_column(:selected_for_next_round, selected)
- Rails.logger.debug { "Value after update: #{@entry_ranking.reload.selected_for_next_round}" }
+ # Update ALL rankings for this entry in this round, not just one
+ entry = @entry_ranking.entry
+ judging_round = @entry_ranking.judging_round
+
+ # Update all rankings for this entry
+ updated = EntryRanking.where(entry: entry, judging_round: judging_round)
+ .update_all(selected_for_next_round: selected)
+
+ if updated > 0
+ Rails.logger.info { "Updated #{updated} rankings for entry #{entry.id} - selected_for_next_round: #{selected}" }
+
respond_to do |format|
format.html {
redirect_back(fallback_location: container_contest_description_contest_instance_judging_round_path(
@@ -57,11 +65,11 @@ def select_for_next_round
flash.now[:notice] = 'Entry selection updated successfully'
render turbo_stream: [
turbo_stream.replace(
- "selected_for_next_round_#{@entry_ranking.entry.id}",
+ "selected_for_next_round_#{entry.id}",
partial: 'judging_rounds/entry_checkbox',
locals: {
- entry: @entry_ranking.entry,
- judging_round: @judging_round,
+ entry: entry,
+ judging_round: judging_round,
container: @container,
contest_description: @contest_description,
contest_instance: @contest_instance
@@ -80,11 +88,11 @@ def select_for_next_round
}
format.turbo_stream {
render turbo_stream: turbo_stream.replace(
- "selected_for_next_round_#{@entry_ranking.entry.id}",
+ "selected_for_next_round_#{entry.id}",
partial: 'judging_rounds/entry_checkbox',
locals: {
- entry: @entry_ranking.entry,
- judging_round: @judging_round,
+ entry: entry,
+ judging_round: judging_round,
container: @container,
contest_description: @contest_description,
contest_instance: @contest_instance
diff --git a/app/controllers/judge_dashboard_controller.rb b/app/controllers/judge_dashboard_controller.rb
index 31700392..aafd1512 100644
--- a/app/controllers/judge_dashboard_controller.rb
+++ b/app/controllers/judge_dashboard_controller.rb
@@ -5,25 +5,35 @@ class JudgeDashboardController < ApplicationController
def index
authorize :judge_dashboard
+ # Find contest instances where the judge has at least one active assignment to an incomplete round
+ contest_instances_with_incomplete_assigned_rounds = JudgingRound
+ .joins(:round_judge_assignments)
+ .where(completed: false)
+ .where(round_judge_assignments: { user_id: current_user.id, active: true })
+ .select(:contest_instance_id)
+ .distinct
+
@judging_assignments = current_user.judging_assignments
- .includes(contest_instance: [
- contest_description: [ :container ],
- judging_rounds: [ :round_judge_assignments ],
- entries: [ :entry_rankings ]
- ])
- .where(active: true)
- .order('judging_rounds.round_number ASC')
+ .joins(:contest_instance)
+ .includes(contest_instance: [
+ contest_description: [ :container ],
+ judging_rounds: [ :round_judge_assignments ],
+ entries: [ :entry_rankings ]
+ ])
+ .where(judging_assignments: { active: true })
+ .where(contest_instances: { active: true, id: contest_instances_with_incomplete_assigned_rounds })
+ .order('judging_rounds.round_number ASC')
# Filter to only include rounds that are active and the judge is actively assigned to
@assigned_rounds = JudgingRound.joins(:round_judge_assignments)
- .where(
- active: true,
- round_judge_assignments: {
- user_id: current_user.id,
- active: true
- }
- )
- .where(contest_instance_id: @judging_assignments.pluck(:contest_instance_id))
+ .where(
+ active: true,
+ round_judge_assignments: {
+ user_id: current_user.id,
+ active: true
+ }
+ )
+ .where(contest_instance_id: @judging_assignments.pluck(:contest_instance_id))
@entry_rankings = EntryRanking.includes(:judging_round)
.where(user: current_user)
diff --git a/app/javascript/controllers/contest_activation_controller.js b/app/javascript/controllers/contest_activation_controller.js
new file mode 100644
index 00000000..ad20a32b
--- /dev/null
+++ b/app/javascript/controllers/contest_activation_controller.js
@@ -0,0 +1,69 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = ["activeCheckbox", "submitButton"]
+ static values = {
+ isNewRecord: Boolean,
+ confirmMessage: String
+ }
+
+ connect() {
+ console.log("contest-activation controller connected")
+ console.log("isNewRecord:", this.isNewRecordValue)
+ console.log("hasSubmitButtonTarget:", this.hasSubmitButtonTarget)
+ if (this.isNewRecordValue && this.hasSubmitButtonTarget) {
+ this.submitButtonTarget.addEventListener('click', this.handleButtonClick.bind(this))
+ console.log("Event listener added to submit button")
+ }
+ }
+
+ handleButtonClick(event) {
+ console.log("=== handleButtonClick called ===")
+ console.log("isNewRecord:", this.isNewRecordValue)
+ console.log("checkbox checked:", this.activeCheckboxTarget.checked)
+
+ // Only prompt for new records (creation)
+ if (!this.isNewRecordValue) {
+ console.log("Not a new record, returning")
+ return
+ }
+
+ // If active checkbox is already checked, proceed normally
+ if (this.activeCheckboxTarget.checked) {
+ console.log("Checkbox already checked, proceeding normally")
+ return
+ }
+
+ // Prevent the default form submission temporarily
+ event.preventDefault()
+
+ // Show confirmation dialog synchronously
+ const message = this.confirmMessageValue ||
+ "This contest will be created as inactive. Would you like to make it active now? " +
+ "Active contests allow administrators to manage entries and judges to provide feedback during their assigned periods."
+
+ const userConfirmed = confirm(message)
+ console.log("User confirmed:", userConfirmed)
+
+ if (userConfirmed) {
+ // User wants to activate - check the checkbox
+ this.activeCheckboxTarget.checked = true
+ console.log("Checkbox set to checked")
+ } else {
+ console.log("User cancelled, checkbox remains unchecked")
+ }
+
+ // Now submit the form using requestSubmit to trigger proper form validation
+ console.log("Submitting form using requestSubmit")
+ if (this.submitButtonTarget.form.requestSubmit) {
+ this.submitButtonTarget.form.requestSubmit(this.submitButtonTarget)
+ } else {
+ // Fallback for older browsers
+ this.submitButtonTarget.form.submit()
+ }
+ }
+
+ checkActivation(event) {
+ // This method is no longer used
+ }
+}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index 1cd7cd66..d4b7e30a 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -19,6 +19,9 @@ application.register("comments-counter", CommentsCounterController)
import ConfirmController from "./confirm_controller"
application.register("confirm", ConfirmController)
+import ContestActivationController from "./contest_activation_controller"
+application.register("contest-activation", ContestActivationController)
+
import DropdownController from "./dropdown_controller"
application.register("dropdown", DropdownController)
diff --git a/app/views/contest_descriptions/_form.html.erb b/app/views/contest_descriptions/_form.html.erb
index a8dacf87..3ddcc02d 100644
--- a/app/views/contest_descriptions/_form.html.erb
+++ b/app/views/contest_descriptions/_form.html.erb
@@ -1,7 +1,11 @@
<%= simple_form_for([@container, @contest_description],
html: {
- id: 'new_contest_description_form'
+ id: 'new_contest_description_form',
+ data: {
+ controller: "contest-activation",
+ contest_activation_is_new_record_value: @contest_description.new_record?
+ }
}) do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
@@ -12,11 +16,15 @@
<%= f.input :eligibility_rules, as: :rich_text_area, hint: "These notes display on the applicant entry form and on the assigned judges' dashboard" %>
<%= f.input :notes, as: :rich_text_area, hint: "These notes are not visible on the applicant entry form" %>
<%= f.input :short_name %>
- <%= f.input :active, hint: "Toggle this checkbox to make the contest active. When active, administrators will be able to manage the submitted entries. Judges, during their assigned judging period, will be able to view the entries and provide feedback. Note that the contest's instances availabilty for submissions will follow the visibility settings based on specific dates configured in each contest instance. If this checkbox is unchecked, the contest and its instances will remain inactive and hidden from evaluation workflows." %>
+ <%= f.input :active,
+ hint: "Toggle this checkbox to make the contest active. When active, administrators will be able to manage the submitted entries. Judges, during their assigned judging period, will be able to view the entries and provide feedback. Note that the contest's instances availabilty for submissions will follow the visibility settings based on specific dates configured in each contest instance. If this checkbox is unchecked, the contest and its instances will remain inactive and hidden from evaluation workflows.",
+ input_html: { data: { contest_activation_target: "activeCheckbox" } } %>
<% end %>
diff --git a/app/views/contest_instances/_form.html.erb b/app/views/contest_instances/_form.html.erb
index 8487d015..9cab049e 100644
--- a/app/views/contest_instances/_form.html.erb
+++ b/app/views/contest_instances/_form.html.erb
@@ -1,16 +1,20 @@
<%= simple_form_for([@container, @contest_description, @contest_instance],
html: {
data: {
- controller: "form-validation",
+ controller: "form-validation contest-activation",
action: "submit->form-validation#submitForm",
- form_validation_target: "form"
+ form_validation_target: "form",
+ contest_activation_is_new_record_value: @contest_instance.new_record?,
+ contest_activation_confirm_message_value: "This contest instance will be created as inactive. Would you like to make it active now? Active instances accept entries during the specified open and close dates."
}
}) do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
- <%= f.input :active, hint: "Specify whether this contest is currently accepting entries during the contest open and close dates specified below." %>
+ <%= f.input :active,
+ hint: "Specify whether this contest is currently accepting entries during the contest open and close dates specified below.",
+ input_html: { data: { contest_activation_target: "activeCheckbox" } } %>
<%= f.association :contest_description, label_method: :name %>
<% end %>
diff --git a/spec/controllers/contest_descriptions_controller_spec.rb b/spec/controllers/contest_descriptions_controller_spec.rb
new file mode 100644
index 00000000..a2add1e6
--- /dev/null
+++ b/spec/controllers/contest_descriptions_controller_spec.rb
@@ -0,0 +1,203 @@
+require 'rails_helper'
+
+RSpec.describe ContestDescriptionsController, type: :controller do
+ let(:department) { create(:department) }
+ let(:user) { create(:user, :axis_mundi) }
+ let(:container) { create(:container, department: department) }
+ let(:contest_description) { create(:contest_description, :active, container: container) }
+
+ before do
+ sign_in user
+ end
+
+ describe 'GET #new' do
+ it 'assigns a new contest description' do
+ get :new, params: { container_id: container.id }
+ expect(assigns(:contest_description)).to be_a_new(ContestDescription)
+ expect(assigns(:contest_description).container).to eq(container)
+ end
+
+ it 'renders the new template' do
+ get :new, params: { container_id: container.id }
+ expect(response).to render_template(:new)
+ end
+ end
+
+ describe 'POST #create' do
+ let(:valid_attributes) do
+ {
+ name: 'Test Contest',
+ created_by: user.email,
+ eligibility_rules: 'Test rules',
+ notes: 'Test notes',
+ short_name: 'test',
+ active: true
+ }
+ end
+
+ let(:invalid_attributes) do
+ {
+ name: '', # Required field
+ created_by: user.email
+ }
+ end
+
+ context 'with valid parameters' do
+ it 'creates a new contest description' do
+ expect {
+ post :create, params: {
+ container_id: container.id,
+ contest_description: valid_attributes
+ }
+ }.to change(ContestDescription, :count).by(1)
+ end
+
+ it 'redirects to the container page with success notice' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: valid_attributes
+ }
+ expect(response).to redirect_to(container_path(container))
+ expect(flash[:notice]).to be_present
+ end
+
+ it 'creates active contest description when active is true' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: valid_attributes.merge(active: true)
+ }
+ created_contest = ContestDescription.last
+ expect(created_contest.active).to be true
+ end
+
+ it 'creates inactive contest description when active is false' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: valid_attributes.merge(active: false)
+ }
+ created_contest = ContestDescription.last
+ expect(created_contest.active).to be false
+ end
+ end
+
+ context 'with invalid parameters' do
+ it 'does not create a new contest description' do
+ expect {
+ post :create, params: {
+ container_id: container.id,
+ contest_description: invalid_attributes
+ }
+ }.not_to change(ContestDescription, :count)
+ end
+
+ it 'renders the new template with errors' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: invalid_attributes
+ }
+ expect(response).to render_template(:new)
+ expect(response.status).to eq(422)
+ end
+
+ it 'assigns the contest description with errors' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: invalid_attributes
+ }
+ expect(assigns(:contest_description)).to be_a(ContestDescription)
+ expect(assigns(:contest_description).errors).to be_present
+ end
+ end
+
+ context 'with Turbo Stream requests' do
+ it 'responds with turbo stream for successful creation' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: valid_attributes
+ }, as: :turbo_stream
+
+ expect(response.status).to eq(302) # Redirect
+ end
+
+ it 'responds with turbo stream for validation errors' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description: invalid_attributes
+ }, as: :turbo_stream
+
+ expect(response.status).to eq(422)
+ expect(response.content_type).to include('text/vnd.turbo-stream.html')
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:update_attributes) do
+ {
+ name: 'Updated Contest',
+ active: false
+ }
+ end
+
+ context 'with valid parameters' do
+ it 'updates the contest description' do
+ put :update, params: {
+ container_id: container.id,
+ id: contest_description.id,
+ contest_description: update_attributes
+ }
+ contest_description.reload
+ expect(contest_description.name).to eq('Updated Contest')
+ end
+
+ it 'redirects to the container page' do
+ put :update, params: {
+ container_id: container.id,
+ id: contest_description.id,
+ contest_description: update_attributes
+ }
+ expect(response).to redirect_to(container_path(container))
+ end
+ end
+
+ context 'when trying to deactivate with active instances' do
+ let!(:active_instance) { create(:contest_instance, contest_description: contest_description, active: true) }
+
+ it 'prevents deactivation and shows error' do
+ put :update, params: {
+ container_id: container.id,
+ id: contest_description.id,
+ contest_description: { active: "0" }
+ }
+
+ expect(response.status).to eq(422)
+ expect(flash[:alert]).to include('Cannot deactivate contest description while it has active instances')
+ contest_description.reload
+ expect(contest_description.active).to be true # Should remain active
+ end
+
+ it 'responds with turbo stream for active instance prevention' do
+ put :update, params: {
+ container_id: container.id,
+ id: contest_description.id,
+ contest_description: { active: "0" }
+ }, as: :turbo_stream
+
+ expect(response.status).to eq(422)
+ expect(response.content_type).to include('text/vnd.turbo-stream.html')
+ end
+ end
+
+ context 'with invalid parameters' do
+ it 'renders the edit template with errors' do
+ put :update, params: {
+ container_id: container.id,
+ id: contest_description.id,
+ contest_description: { name: '' } # Invalid - name is required
+ }
+ expect(response).to render_template(:edit)
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/contest_instances_controller_create_spec.rb b/spec/controllers/contest_instances_controller_create_spec.rb
new file mode 100644
index 00000000..58394891
--- /dev/null
+++ b/spec/controllers/contest_instances_controller_create_spec.rb
@@ -0,0 +1,259 @@
+require 'rails_helper'
+
+RSpec.describe ContestInstancesController, type: :controller do
+ let(:department) { create(:department) }
+ let(:user) { create(:user, :axis_mundi) }
+ let(:container) { create(:container, department: department) }
+ let(:contest_description) { create(:contest_description, :active, container: container) }
+ let!(:category) { create(:category, kind: 'Fiction_Base') }
+ let!(:class_level) { create(:class_level, name: 'Undergraduate_Base') }
+
+ before do
+ sign_in user
+ end
+
+ describe 'POST #create' do
+ let(:valid_attributes) do
+ {
+ active: true,
+ date_open: 1.week.from_now,
+ date_closed: 2.weeks.from_now,
+ notes: 'Test instance notes',
+ maximum_number_entries_per_applicant: 3,
+ require_pen_name: false,
+ require_campus_employment_info: false,
+ require_finaid_info: false,
+ created_by: user.email,
+ category_ids: [category.id],
+ class_level_ids: [class_level.id]
+ }
+ end
+
+ let(:invalid_attributes) do
+ {
+ active: true,
+ date_open: 2.weeks.from_now,
+ date_closed: 1.week.from_now, # Invalid: close date before open date
+ notes: 'Test instance notes'
+ }
+ end
+
+ context 'with valid parameters' do
+ it 'creates a new contest instance' do
+ expect {
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes
+ }
+ }.to change(ContestInstance, :count).by(1)
+ end
+
+ it 'redirects to the contest instance page with success notice' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes
+ }
+
+ created_instance = ContestInstance.last
+ expect(response).to redirect_to(
+ container_contest_description_contest_instance_path(container, contest_description, created_instance)
+ )
+ expect(flash[:notice]).to be_present
+ end
+
+ it 'creates active contest instance when active is true' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(active: true)
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.active).to be true
+ end
+
+ it 'creates inactive contest instance when active is false' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(active: false)
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.active).to be false
+ end
+
+ it 'sets the created_by field to current user email' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.created_by).to eq(user.email)
+ end
+
+ it 'associates contest instance with correct contest description' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.contest_description).to eq(contest_description)
+ end
+ end
+
+ context 'with invalid parameters' do
+ it 'does not create a new contest instance' do
+ expect {
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: invalid_attributes
+ }
+ }.not_to change(ContestInstance, :count)
+ end
+
+ it 'renders the new template with errors' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: invalid_attributes
+ }
+
+ expect(response).to render_template(:new)
+ expect(response.status).to eq(422)
+ end
+
+ it 'assigns the contest instance with errors' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: invalid_attributes
+ }
+
+ expect(assigns(:contest_instance)).to be_a(ContestInstance)
+ expect(assigns(:contest_instance).errors).to be_present
+ end
+ end
+
+ context 'activation workflow integration' do
+ it 'processes activation parameter correctly when coming from JavaScript workflow' do
+ # Simulate the JavaScript workflow setting active to true after confirmation
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(active: true) # JS would set this to true
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.active).to be true
+ expect(response).to be_redirect
+ end
+
+ it 'respects user choice to keep instance inactive' do
+ # Simulate user choosing to keep instance inactive in JavaScript workflow
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(active: false) # JS would leave this as false
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.active).to be false
+ expect(response).to be_redirect
+ end
+ end
+
+ context 'with categories and class levels' do
+ let!(:category1) { create(:category, kind: 'Fiction') }
+ let!(:category2) { create(:category, kind: 'Poetry') }
+ let!(:class_level1) { create(:class_level, name: 'Undergraduate') }
+ let!(:class_level2) { create(:class_level, name: 'Graduate') }
+
+ it 'associates selected categories with contest instance' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(
+ category_ids: [category1.id, category2.id]
+ )
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.categories).to include(category1, category2)
+ end
+
+ it 'associates selected class levels with contest instance' do
+ post :create, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ contest_instance: valid_attributes.merge(
+ class_level_ids: [class_level1.id, class_level2.id]
+ )
+ }
+
+ created_instance = ContestInstance.last
+ expect(created_instance.class_levels).to include(class_level1, class_level2)
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let!(:contest_instance) { create(:contest_instance, contest_description: contest_description, active: false) }
+
+ let(:update_attributes) do
+ {
+ active: true,
+ notes: 'Updated notes'
+ }
+ end
+
+ context 'with valid parameters' do
+ it 'updates the contest instance' do
+ put :update, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ id: contest_instance.id,
+ contest_instance: update_attributes
+ }
+
+ contest_instance.reload
+ expect(contest_instance.active).to be true
+ expect(contest_instance.notes).to eq('Updated notes')
+ end
+
+ it 'redirects to the contest instance page' do
+ put :update, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ id: contest_instance.id,
+ contest_instance: update_attributes
+ }
+
+ expect(response).to redirect_to(
+ container_contest_description_contest_instance_path(container, contest_description, contest_instance)
+ )
+ end
+ end
+
+ context 'with invalid parameters' do
+ it 'renders the edit template with errors' do
+ put :update, params: {
+ container_id: container.id,
+ contest_description_id: contest_description.id,
+ id: contest_instance.id,
+ contest_instance: { date_closed: 1.day.ago } # Invalid: past date
+ }
+
+ expect(response).to render_template(:edit)
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/entry_rankings_controller_spec.rb b/spec/controllers/entry_rankings_controller_spec.rb
index fa9ece59..a3a56b3c 100644
--- a/spec/controllers/entry_rankings_controller_spec.rb
+++ b/spec/controllers/entry_rankings_controller_spec.rb
@@ -137,7 +137,11 @@
selected_for_next_round: '1'
}
- expect(entry_ranking.reload.selected_for_next_round).to be true
+ # Check that ALL rankings for this entry are updated
+ EntryRanking.where(entry: entry_ranking.entry, judging_round: judging_round).each do |ranking|
+ expect(ranking.reload.selected_for_next_round).to be true
+ end
+
expect(response).to redirect_to(
container_contest_description_contest_instance_judging_round_path(
container, contest_description, contest_instance, judging_round
@@ -147,7 +151,9 @@
end
it 'allows deselection of entry for next round' do
- entry_ranking.update(selected_for_next_round: true)
+ # First set all rankings to selected
+ EntryRanking.where(entry: entry_ranking.entry, judging_round: judging_round)
+ .update_all(selected_for_next_round: true)
patch :select_for_next_round, params: {
container_id: container.id,
@@ -158,7 +164,11 @@
selected_for_next_round: '0'
}
- expect(entry_ranking.reload.selected_for_next_round).to be false
+ # Check that ALL rankings for this entry are updated
+ EntryRanking.where(entry: entry_ranking.entry, judging_round: judging_round).each do |ranking|
+ expect(ranking.reload.selected_for_next_round).to be false
+ end
+
expect(flash[:notice]).to eq('Entry selection updated successfully.')
end
end
diff --git a/spec/controllers/judge_dashboard_controller_spec.rb b/spec/controllers/judge_dashboard_controller_spec.rb
index 5274ee6a..ea6e554a 100644
--- a/spec/controllers/judge_dashboard_controller_spec.rb
+++ b/spec/controllers/judge_dashboard_controller_spec.rb
@@ -3,15 +3,6 @@
RSpec.describe JudgeDashboardController, type: :controller do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
-
- # Start at a known point in time
- before(:all) do
- travel_to Time.zone.local(2024, 2, 1, 12, 0, 0)
- end
-
- after(:all) do
- end
-
let(:contest_instance) do
create(:contest_instance,
contest_description: contest_description,
@@ -43,6 +34,7 @@
end
before do
+ travel_to Time.zone.local(2024, 2, 1, 12, 0, 0)
sign_in judge
end
@@ -135,6 +127,35 @@
get :index
end
+ context 'when contest_instance is inactive' do
+ it 'does not display the contest in the judge dashboard' do
+ contest_instance = create(:contest_instance, active: false, contest_description: contest_description)
+ judging_round = create(:judging_round, contest_instance: contest_instance, active: true, completed: false)
+ create(:judging_assignment, user: judge, contest_instance: contest_instance, active: true)
+ create(:round_judge_assignment, user: judge, judging_round: judging_round, active: true)
+
+ get :index
+
+ expect(assigns(:judging_assignments)).to be_empty
+ expect(response.body).not_to include(contest_instance.contest_description.name)
+ end
+ end
+
+ context 'when contest_instance is active but the assigned judging round is completed' do
+ it 'does not display the contest in the judge dashboard' do
+ # contest_instance = create(:contest_instance, active: true, contest_description: contest_description)
+ # judging_round = create(:judging_round, contest_instance: contest_instance, active: true, completed: true)
+ create(:judging_assignment, user: judge, contest_instance: contest_instance, active: true)
+ create(:round_judge_assignment, user: judge, judging_round: round_1)
+ round_1.update!(completed: true)
+
+ get :index
+
+ expect(assigns(:judging_assignments)).to be_empty
+ expect(response.body).not_to include(contest_instance.contest_description.name)
+ end
+ end
+
it 'assigns empty @judging_assignments' do
expect(assigns(:judging_assignments)).to be_empty
end
diff --git a/spec/features/judging_round_selection_spec.rb b/spec/features/judging_round_selection_spec.rb
index 85afd56c..fd03971b 100644
--- a/spec/features/judging_round_selection_spec.rb
+++ b/spec/features/judging_round_selection_spec.rb
@@ -91,32 +91,51 @@
end
it 'allows deselecting entries', :js do
- # First select an entry (this will be HTML format)
+ # First select an entry
within('tr', text: 'Second Entry') do
- find("input[name='selected_for_next_round']").click
+ checkbox = find("input[name='selected_for_next_round']")
+ expect(checkbox).not_to be_checked
+ checkbox.click
end
# Wait for the flash message to appear
expect(page).to have_css('.alert.alert-success', text: 'Entry selection updated successfully', wait: 5)
- # Now verify the database state after the first click
- entry2_ranking = EntryRanking.find_by(entry: entry2, judging_round: judging_round)
- expect(entry2_ranking.selected_for_next_round).to be true
+ # Verify ALL rankings for this entry are now selected
+ expect(EntryRanking.where(entry: entry2, judging_round: judging_round, selected_for_next_round: true).count).to eq(2)
+
+ # Verify the checkbox is now checked
+ within('tr', text: 'Second Entry') do
+ checkbox = find("input[name='selected_for_next_round']")
+ expect(checkbox).to be_checked
+ end
- # Then deselect it (this will be Turbo Stream format)
+ # Then deselect it
within('tr', text: 'Second Entry') do
find("input[name='selected_for_next_round']").click
end
# Wait for the checkbox to be unchecked via Turbo Stream
- expect(page).to have_css("input[name='selected_for_next_round']:not(:checked)", wait: 5)
+ within('tr', text: 'Second Entry') do
+ expect(page).to have_css("input[name='selected_for_next_round']:not(:checked)", wait: 10)
+ end
# Wait for the flash message to appear
expect(page).to have_css('.alert.alert-success', text: 'Entry selection updated successfully', wait: 5)
- # Force a reload and wait for the database to be updated
- entry2_ranking.reload
- expect(entry2_ranking.selected_for_next_round).to be false
+ # Use a retry loop to wait for the database state to change
+ Timeout.timeout(5) do
+ loop do
+ if EntryRanking.where(entry: entry2, judging_round: judging_round, selected_for_next_round: false).count == 2
+ break
+ end
+ sleep 0.1
+ end
+ end
+
+ # Final verification
+ expect(EntryRanking.where(entry: entry2, judging_round: judging_round, selected_for_next_round: false).count).to eq(2)
+ expect(EntryRanking.where(entry: entry2, judging_round: judging_round, selected_for_next_round: true).count).to eq(0)
end
it 'shows judge comments when clicking view comments', :js do
diff --git a/spec/javascript/controllers/contest_activation_controller.test.js b/spec/javascript/controllers/contest_activation_controller.test.js
new file mode 100644
index 00000000..24e106f2
--- /dev/null
+++ b/spec/javascript/controllers/contest_activation_controller.test.js
@@ -0,0 +1,335 @@
+import { Application } from "@hotwired/stimulus"
+import ContestActivationController from "../../../app/javascript/controllers/contest_activation_controller"
+
+describe("ContestActivationController", () => {
+ let application
+ let container
+
+ beforeEach(() => {
+ container = document.createElement("div")
+ document.body.appendChild(container)
+
+ application = Application.start()
+ application.register("contest-activation", ContestActivationController)
+ })
+
+ afterEach(() => {
+ document.body.removeChild(container)
+ application.stop()
+ })
+
+ describe("when creating a new record", () => {
+ beforeEach(() => {
+ container.innerHTML = `
+
+ `
+ })
+
+ describe("when active checkbox is checked", () => {
+ it("submits form normally without confirmation", () => {
+ const checkbox = container.querySelector('[data-contest-activation-target="activeCheckbox"]')
+ const submitButton = container.querySelector('[data-contest-activation-target="submitButton"]')
+ const form = container.querySelector('form')
+
+ checkbox.checked = true
+
+ // Mock form submission - listen for submit event
+ let formSubmitted = false
+ form.addEventListener('submit', (e) => {
+ e.preventDefault()
+ formSubmitted = true
+ })
+
+ // Mock requestSubmit if it doesn't exist
+ if (!form.requestSubmit) {
+ form.requestSubmit = function() {
+ const submitEvent = new Event('submit', { cancelable: true, bubbles: true })
+ this.dispatchEvent(submitEvent)
+ }
+ }
+
+ // Click submit button - this will trigger the controller's event listener
+ submitButton.click()
+
+ expect(formSubmitted).toBe(true)
+ })
+ })
+
+ describe("when active checkbox is not checked", () => {
+ it("shows confirmation dialog and activates if user confirms", () => {
+ const checkbox = container.querySelector('[data-contest-activation-target="activeCheckbox"]')
+ const submitButton = container.querySelector('[data-contest-activation-target="submitButton"]')
+ const form = container.querySelector('form')
+
+ checkbox.checked = false
+
+ // Mock window.confirm to return true (user confirms)
+ const originalConfirm = window.confirm
+ let confirmMessage = ''
+ window.confirm = (message) => {
+ confirmMessage = message
+ return true
+ }
+
+ // Mock form submission
+ let formSubmitted = false
+ form.addEventListener('submit', (e) => {
+ e.preventDefault()
+ formSubmitted = true
+ })
+
+ // Mock requestSubmit if it doesn't exist
+ if (!form.requestSubmit) {
+ form.requestSubmit = function() {
+ const submitEvent = new Event('submit', { cancelable: true, bubbles: true })
+ this.dispatchEvent(submitEvent)
+ }
+ }
+
+ try {
+ // Click submit button
+ submitButton.click()
+
+ // Check that confirmation was shown
+ expect(confirmMessage).toContain('This contest will be created as inactive')
+ expect(confirmMessage).toContain('Would you like to make it active now?')
+
+ // Check that checkbox was checked after confirmation
+ expect(checkbox.checked).toBe(true)
+
+ // Form should be submitted
+ expect(formSubmitted).toBe(true)
+ } finally {
+ window.confirm = originalConfirm
+ }
+ })
+
+ it("keeps checkbox unchecked if user cancels", () => {
+ const checkbox = container.querySelector('[data-contest-activation-target="activeCheckbox"]')
+ const submitButton = container.querySelector('[data-contest-activation-target="submitButton"]')
+ const form = container.querySelector('form')
+
+ checkbox.checked = false
+
+ // Mock window.confirm to return false (user cancels)
+ const originalConfirm = window.confirm
+ window.confirm = () => false
+
+ // Mock form submission
+ let formSubmitted = false
+ form.addEventListener('submit', (e) => {
+ e.preventDefault()
+ formSubmitted = true
+ })
+
+ // Mock requestSubmit if it doesn't exist
+ if (!form.requestSubmit) {
+ form.requestSubmit = function() {
+ const submitEvent = new Event('submit', { cancelable: true, bubbles: true })
+ this.dispatchEvent(submitEvent)
+ }
+ }
+
+ try {
+ // Click submit button
+ submitButton.click()
+
+ // Check that checkbox remains unchecked
+ expect(checkbox.checked).toBe(false)
+
+ // Form should still be submitted
+ expect(formSubmitted).toBe(true)
+ } finally {
+ window.confirm = originalConfirm
+ }
+ })
+
+ it("uses custom confirmation message when provided", (done) => {
+ // Mock window.confirm FIRST
+ const originalConfirm = window.confirm
+ let confirmMessage = ''
+ window.confirm = (message) => {
+ confirmMessage = message
+ return true
+ }
+
+ // Mock HTMLFormElement.prototype.requestSubmit globally for JSDOM
+ const originalRequestSubmit = HTMLFormElement.prototype.requestSubmit
+ HTMLFormElement.prototype.requestSubmit = function(submitter) {
+ const submitEvent = new Event('submit', { cancelable: true, bubbles: true })
+ // Set the submitter property on the event
+ Object.defineProperty(submitEvent, 'submitter', {
+ value: submitter,
+ writable: false
+ })
+ this.dispatchEvent(submitEvent)
+ }
+
+ try {
+ // Create container and add HTML with custom confirmation message
+ container.innerHTML = `
+
+ `
+
+ // Wait for Stimulus to connect the controller
+ setTimeout(() => {
+ const checkbox = container.querySelector('[data-contest-activation-target="activeCheckbox"]')
+ const submitButton = container.querySelector('[data-contest-activation-target="submitButton"]')
+ const form = container.querySelector('form')
+
+ checkbox.checked = false
+
+ // Mock form submission
+ let formSubmitted = false
+ form.addEventListener('submit', (e) => {
+ e.preventDefault()
+ formSubmitted = true
+ })
+
+ // Click the submit button
+ submitButton.click()
+
+ // Verify the custom confirmation message was shown
+ expect(confirmMessage).toBe('Custom message for testing')
+
+ // Check that checkbox was checked after confirmation
+ expect(checkbox.checked).toBe(true)
+
+ // Form should be submitted
+ expect(formSubmitted).toBe(true)
+
+ done()
+ }, 50) // Give Stimulus time to initialize
+ } catch (error) {
+ // Restore original methods in case of error
+ window.confirm = originalConfirm
+ if (originalRequestSubmit) {
+ HTMLFormElement.prototype.requestSubmit = originalRequestSubmit
+ } else {
+ delete HTMLFormElement.prototype.requestSubmit
+ }
+ done(error)
+ } finally {
+ // Clean up will happen after the test completes
+ setTimeout(() => {
+ window.confirm = originalConfirm
+ if (originalRequestSubmit) {
+ HTMLFormElement.prototype.requestSubmit = originalRequestSubmit
+ } else {
+ delete HTMLFormElement.prototype.requestSubmit
+ }
+ }, 100)
+ }
+ })
+ })
+ })
+
+ describe("when editing an existing record", () => {
+ beforeEach(() => {
+ container.innerHTML = `
+
+ `
+ })
+
+ it("submits form normally without any confirmation", () => {
+ const checkbox = container.querySelector('[data-contest-activation-target="activeCheckbox"]')
+ const submitButton = container.querySelector('[data-contest-activation-target="submitButton"]')
+ const form = container.querySelector('form')
+
+ checkbox.checked = false // Even when unchecked
+
+ // Mock form submission
+ let formSubmitted = false
+ form.addEventListener('submit', (e) => {
+ e.preventDefault()
+ formSubmitted = true
+ })
+
+ // Mock window.confirm (should not be called)
+ const originalConfirm = window.confirm
+ let confirmCalled = false
+ window.confirm = () => {
+ confirmCalled = true
+ return true
+ }
+
+ try {
+ // Click submit button - since isNewRecord is false,
+ // the controller won't interfere and normal form submission should occur
+ submitButton.click()
+
+ expect(confirmCalled).toBe(false)
+ expect(formSubmitted).toBe(true)
+ } finally {
+ window.confirm = originalConfirm
+ }
+ })
+ })
+
+ describe("controller connection", () => {
+ it("logs connection message", (done) => {
+ // Save original console.log
+ const originalLog = console.log
+ const capturedLogs = []
+
+ // Mock console.log BEFORE creating any controller
+ console.log = (...args) => {
+ capturedLogs.push(args.join(' '))
+ originalLog(...args) // Also call original to help debug
+ }
+
+ try {
+ // Create a new container with the controller
+ const testContainer = document.createElement('div')
+ testContainer.innerHTML = `
+
+
+
+
+ `
+
+ // Add to DOM - this will trigger Stimulus to instantiate the controller
+ // and call its connect() method
+ document.body.appendChild(testContainer)
+
+ // Wait a bit for Stimulus to process
+ setTimeout(() => {
+ // The connection should have been logged
+ const hasConnectionLog = capturedLogs.some(log => log.includes('contest-activation controller connected'))
+ expect(hasConnectionLog).toBe(true)
+
+ // Cleanup
+ document.body.removeChild(testContainer)
+ console.log = originalLog
+ done()
+ }, 50)
+ } catch (error) {
+ console.log = originalLog
+ done(error)
+ }
+ })
+ })
+})
diff --git a/spec/javascript/setup.js b/spec/javascript/setup.js
index 918d3d94..2ffd04bb 100644
--- a/spec/javascript/setup.js
+++ b/spec/javascript/setup.js
@@ -25,6 +25,14 @@ global.ResizeObserver = class ResizeObserver {
disconnect() {}
}
+// Add HTMLFormElement.prototype.requestSubmit polyfill for JSDOM
+if (!HTMLFormElement.prototype.requestSubmit) {
+ HTMLFormElement.prototype.requestSubmit = function() {
+ const submitEvent = new Event('submit', { cancelable: true, bubbles: true })
+ this.dispatchEvent(submitEvent)
+ }
+}
+
// Add any missing DOM methods that might be required by your tests
Object.defineProperty(window, 'matchMedia', {
writable: true,
diff --git a/spec/system/contest_activation_spec.rb b/spec/system/contest_activation_spec.rb
new file mode 100644
index 00000000..f5950a43
--- /dev/null
+++ b/spec/system/contest_activation_spec.rb
@@ -0,0 +1,272 @@
+require 'rails_helper'
+
+RSpec.describe 'Contest Activation Workflow', type: :system, js: true do
+ let(:department) { create(:department) }
+ let(:user) { create(:user, :axis_mundi) }
+ let(:container) { create(:container, department: department) }
+
+ before do
+ # Create required EditableContent records for form rendering
+ create(:editable_content,
+ page: 'profiles',
+ section: 'finaid_information')
+ create(:editable_content,
+ page: 'contest_instances',
+ section: 'instructions')
+
+ # Create required categories and class levels for contest instances
+ create(:category, kind: 'Fiction')
+ create(:class_level, name: 'Undergraduate')
+
+ sign_in user
+ end
+
+ describe 'Contest Description activation workflow' do
+ context 'when creating a new contest description' do
+ it 'shows confirmation dialog when active checkbox is unchecked' do
+ visit new_container_contest_description_path(container)
+
+ fill_in 'Name', with: 'Test Contest'
+ fill_in 'Short name', with: 'test'
+
+ # Ensure active checkbox is unchecked
+ uncheck 'Active'
+
+ # Mock the confirm dialog to accept
+ page.execute_script("window.confirm = function() { return true; }")
+
+ click_button 'Create Contest'
+
+ # Should create an active contest (checkbox was checked by JS)
+ expect(page).to have_current_path(container_path(container))
+ expect(page).to have_content('Contest description was successfully created')
+
+ created_contest = ContestDescription.last
+ expect(created_contest.active).to be true
+ expect(created_contest.name).to eq('Test Contest')
+ end
+
+ it 'keeps contest inactive when user cancels confirmation' do
+ visit new_container_contest_description_path(container)
+
+ fill_in 'Name', with: 'Test Contest'
+ fill_in 'Short name', with: 'test'
+
+ # Ensure active checkbox is unchecked
+ uncheck 'Active'
+
+ # Disable the JavaScript confirmation entirely to test the inactive submission
+ page.execute_script(<<~JS)
+ // Remove the event listener to bypass the confirmation
+ const submitButton = document.querySelector('[data-contest-activation-target="submitButton"]');
+ if (submitButton) {
+ const newButton = submitButton.cloneNode(true);
+ submitButton.parentNode.replaceChild(newButton, submitButton);
+ }
+ JS
+
+ click_button 'Create Contest'
+
+ # Should create an inactive contest
+ expect(page).to have_current_path(container_path(container))
+ expect(page).to have_content('Contest description was successfully created')
+
+ created_contest = ContestDescription.last
+ expect(created_contest.active).to be false
+ expect(created_contest.name).to eq('Test Contest')
+ end
+
+ it 'submits normally when active checkbox is already checked' do
+ visit new_container_contest_description_path(container)
+
+ fill_in 'Name', with: 'Test Contest'
+ fill_in 'Short name', with: 'test'
+
+ # Keep active checkbox checked
+ check 'Active'
+
+ # Set up a spy to ensure confirm is not called
+ page.execute_script("window.confirmCalled = false; window.confirm = function() { window.confirmCalled = true; return true; }")
+
+ click_button 'Create Contest'
+
+ # Should create an active contest without showing confirmation
+ expect(page).to have_current_path(container_path(container))
+ expect(page).to have_content('Contest description was successfully created')
+
+ created_contest = ContestDescription.last
+ expect(created_contest.active).to be true
+
+ # Confirm should not have been called
+ confirm_called = page.execute_script("return window.confirmCalled")
+ expect(confirm_called).to be false
+ end
+
+ it 'displays validation errors properly when form is invalid' do
+ visit new_container_contest_description_path(container)
+
+ # Leave name blank (required field)
+ fill_in 'Short name', with: 'test'
+ uncheck 'Active'
+
+ # Mock the confirm dialog to accept
+ page.execute_script("window.confirm = function() { return true; }")
+
+ click_button 'Create Contest'
+
+ # Should stay on new page with errors
+ expect(page).to have_current_path(new_container_contest_description_path(container))
+ expect(page).to have_content("Name can't be blank")
+ expect(ContestDescription.count).to eq(0)
+ end
+ end
+
+ context 'when editing an existing contest description' do
+ let!(:contest_description) { create(:contest_description, container: container, active: true) }
+
+ it 'does not show confirmation dialog even when unchecking active' do
+ visit edit_container_contest_description_path(container, contest_description)
+
+ uncheck 'Active'
+
+ # Set up a spy to ensure confirm is not called
+ page.execute_script("window.confirmCalled = false; window.confirm = function() { window.confirmCalled = true; return true; }")
+
+ click_button 'Update Contest'
+
+ # Should update without confirmation
+ expect(page).to have_current_path(container_path(container))
+ expect(page).to have_content('Contest description was successfully updated')
+
+ # Confirm should not have been called
+ confirm_called = page.execute_script("return window.confirmCalled")
+ expect(confirm_called).to be false
+
+ contest_description.reload
+ expect(contest_description.active).to be false
+ end
+ end
+ end
+
+ describe 'Contest Instance activation workflow' do
+ let!(:contest_description) { create(:contest_description, container: container, active: true) }
+
+ context 'when creating a new contest instance' do
+ it 'shows confirmation dialog when active checkbox is unchecked' do
+ visit new_container_contest_description_contest_instance_path(container, contest_description)
+
+ # Fill in required fields using JavaScript to bypass any form processing
+ page.execute_script("document.getElementById('contest_instance_date_open').value = '2025-03-01T12:00'")
+ page.execute_script("document.getElementById('contest_instance_date_closed').value = '2025-04-01T12:00'")
+
+ # Select required categories and class levels
+ check 'Fiction'
+ check 'Undergraduate'
+
+ # Ensure active checkbox is unchecked
+ uncheck 'Active'
+
+ # Mock the confirm dialog to accept
+ page.execute_script("window.confirm = function(message) { window.lastConfirmMessage = message; return true; }")
+
+ click_button 'Create Contest instance'
+
+ # Should create an active contest instance
+ expect(page).to have_content('Contest instance was successfully created')
+
+ created_instance = contest_description.contest_instances.last
+ expect(created_instance.active).to be true
+
+ # Check that the correct message was shown
+ confirm_message = page.execute_script("return window.lastConfirmMessage")
+ expect(confirm_message).to include('This contest instance will be created as inactive')
+ expect(confirm_message).to include('Active instances accept entries during the specified open and close dates')
+ end
+
+ it 'keeps instance inactive when user cancels confirmation' do
+ visit new_container_contest_description_contest_instance_path(container, contest_description)
+
+ page.execute_script("document.getElementById('contest_instance_date_open').value = '2025-03-01T12:00'")
+ page.execute_script("document.getElementById('contest_instance_date_closed').value = '2025-04-01T12:00'")
+
+ # Select required categories and class levels
+ check 'Fiction'
+ check 'Undergraduate'
+
+ uncheck 'Active'
+
+ # Disable the JavaScript confirmation entirely to test the inactive submission
+ page.execute_script(<<~JS)
+ // Remove the event listener to bypass the confirmation
+ const submitButton = document.querySelector('[data-contest-activation-target="submitButton"]');
+ if (submitButton) {
+ const newButton = submitButton.cloneNode(true);
+ submitButton.parentNode.replaceChild(newButton, submitButton);
+ }
+ JS
+
+ click_button 'Create Contest instance'
+
+ # Should create an inactive contest instance - check for the instance details instead of success message
+ expect(page).to have_content('Active:')
+ expect(page).to have_content('No') # Active: No
+
+ created_instance = contest_description.contest_instances.last
+ expect(created_instance.active).to be false
+ end
+
+ it 'submits normally when active checkbox is already checked' do
+ visit new_container_contest_description_contest_instance_path(container, contest_description)
+
+ page.execute_script("document.getElementById('contest_instance_date_open').value = '2025-03-01T12:00'")
+ page.execute_script("document.getElementById('contest_instance_date_closed').value = '2025-04-01T12:00'")
+
+ # Select required categories and class levels
+ check 'Fiction'
+ check 'Undergraduate'
+
+ check 'Active'
+
+ # Set up a spy to ensure confirm is not called
+ page.execute_script("window.confirmCalled = false; window.confirm = function() { window.confirmCalled = true; return true; }")
+
+ click_button 'Create Contest instance'
+
+ # Should create an active instance without confirmation
+ expect(page).to have_content('Contest instance was successfully created')
+
+ created_instance = contest_description.contest_instances.last
+ expect(created_instance.active).to be true
+
+ # Confirm should not have been called
+ confirm_called = page.execute_script("return window.confirmCalled")
+ expect(confirm_called).to be false
+ end
+ end
+
+ context 'when editing an existing contest instance' do
+ let!(:contest_instance) { create(:contest_instance, contest_description: contest_description, active: true) }
+
+ it 'does not show confirmation dialog when editing' do
+ visit edit_container_contest_description_contest_instance_path(container, contest_description, contest_instance)
+
+ uncheck 'Active'
+
+ # Set up a spy to ensure confirm is not called
+ page.execute_script("window.confirmCalled = false; window.confirm = function() { window.confirmCalled = true; return true; }")
+
+ click_button 'Update Contest instance'
+
+ # Should update without confirmation
+ expect(page).to have_content('Contest instance was successfully created/updated')
+
+ # Confirm should not have been called
+ confirm_called = page.execute_script("return window.confirmCalled")
+ expect(confirm_called).to be false
+
+ contest_instance.reload
+ expect(contest_instance.active).to be false
+ end
+ end
+ end
+end
diff --git a/spec/system/judge_round_visibility_spec.rb b/spec/system/judge_round_visibility_spec.rb
index f6ff55e9..2e36b9d0 100644
--- a/spec/system/judge_round_visibility_spec.rb
+++ b/spec/system/judge_round_visibility_spec.rb
@@ -112,16 +112,14 @@
it 'shows appropriate message for no round assignment' do
travel_to(1.day.from_now) do
- expect(page).to have_content('No active rounds assigned to you')
+ expect(page).to have_content('You have not been assigned to judge any contests.')
end
end
it 'does not show judging interface' do
travel_to(1.day.from_now) do
visit judge_dashboard_path # Reload page within time travel block
- find('.accordion-button').click
- expect(page).to have_content('No active rounds assigned to you')
- expect(page).to have_no_css('[data-controller="entry-drag"]')
+ expect(page).to have_content('You have not been assigned to judge any contests.')
end
end
end
@@ -137,16 +135,14 @@
it 'shows appropriate message for no active assignment' do
travel_to(1.day.from_now) do
- expect(page).to have_content('No active rounds assigned to you')
+ expect(page).to have_content('You have not been assigned to judge any contests.')
end
end
it 'does not show judging interface' do
travel_to(1.day.from_now) do
visit judge_dashboard_path # Reload page within time travel block
- find('.accordion-button').click
- expect(page).to have_content('No active rounds assigned to you')
- expect(page).to have_no_css('[data-controller="entry-drag"]')
+ expect(page).to have_content('You have not been assigned to judge any contests.')
end
end
end
diff --git a/spec/system/judging_rounds_spec.rb b/spec/system/judging_rounds_spec.rb
index 287a1d30..13bf4745 100644
--- a/spec/system/judging_rounds_spec.rb
+++ b/spec/system/judging_rounds_spec.rb
@@ -9,7 +9,8 @@
let(:contest_instance) { create(:contest_instance,
contest_description: contest_description,
date_open: 2.days.ago,
- date_closed: 1.day.ago
+ date_closed: 1.day.ago,
+ active: true
) }
before do
@@ -69,6 +70,7 @@
)
judge = create(:user, :with_judge_role)
create(:judging_assignment, user: judge, contest_instance: contest_instance)
+ create(:round_judge_assignment, user: judge, judging_round: judging_round)
sign_in judge
visit judge_dashboard_path