diff --git a/app/controllers/mats_controller.rb b/app/controllers/mats_controller.rb index 7d3745a..15b0603 100644 --- a/app/controllers/mats_controller.rb +++ b/app/controllers/mats_controller.rb @@ -1,22 +1,21 @@ class MatsController < ApplicationController before_action :set_mat, only: [:show, :edit, :update, :destroy, :assign_next_match] before_action :check_access, only: [:new,:create,:update,:destroy,:edit,:show, :assign_next_match] - before_action :check_for_matches, only: [:show] # GET /mats/1 # GET /mats/1.json def show - bout_number_param = params[:bout_number] # Read the bout_number from the URL params - - if bout_number_param - @show_next_bout_button = false - @match = @mat.queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i } + bout_number_param = params[:bout_number] + @queue_matches = @mat.queue_matches + @match = if bout_number_param + @queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i } else - @show_next_bout_button = true - @match = @mat.queue1_match + @queue_matches[0] end - - @next_match = @mat.queue2_match # Second match on the mat + # If a requested bout is no longer queued, fall back to queue1. + @match ||= @queue_matches[0] + @next_match = @queue_matches[1] + @show_next_bout_button = false @wrestlers = [] if @match @@ -142,11 +141,4 @@ class MatsController < ApplicationController end - def check_for_matches - if @mat - if @mat.tournament.matches.empty? - redirect_to "/tournaments/#{@tournament.id}/no_matches" - end - end - end end diff --git a/app/models/mat.rb b/app/models/mat.rb index 2ef4085..9843e6f 100644 --- a/app/models/mat.rb +++ b/app/models/mat.rb @@ -184,6 +184,7 @@ class Mat < ApplicationRecord def clear_queue! update!(queue1: nil, queue2: nil, queue3: nil, queue4: nil) + broadcast_current_match end def unfinished_matches diff --git a/app/models/tournament.rb b/app/models/tournament.rb index a5f273b..918ade2 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -97,6 +97,7 @@ class Tournament < ApplicationRecord def destroy_all_matches matches.destroy_all + mats.each(&:clear_queue!) end def matches_by_round(round) diff --git a/app/services/tournament_services/wipe_tournament_matches.rb b/app/services/tournament_services/wipe_tournament_matches.rb index 1738a8c..d666c8b 100644 --- a/app/services/tournament_services/wipe_tournament_matches.rb +++ b/app/services/tournament_services/wipe_tournament_matches.rb @@ -14,10 +14,10 @@ class WipeTournamentMatches end def wipeMatches - @tournament.matches.destroy_all + @tournament.destroy_all_matches end def resetSchoolScores @tournament.schools.update_all("score = 0.0") end -end \ No newline at end of file +end diff --git a/app/views/matches/_matchstats.html.erb b/app/views/matches/_matchstats.html.erb index 3688bbc..4c58f53 100644 --- a/app/views/matches/_matchstats.html.erb +++ b/app/views/matches/_matchstats.html.erb @@ -18,8 +18,19 @@

Bout <%= @match.bout_number %>

- <% if @show_next_bout_button && @next_match %> - <%= link_to "Skip to Next Match for Mat #{@mat.name}", mat_path(@mat, bout_number: @next_match.bout_number), class: "btn btn-primary" %> + <% if @mat %> + <% queue_matches = @queue_matches || @mat.queue_matches %> +
+ <% queue_matches.each_with_index do |queue_match, index| %> + <% queue_label = "Queue #{index + 1}" %> + <% if queue_match %> + <% button_class = queue_match.id == @match.id ? "btn btn-success btn-sm" : "btn btn-primary btn-sm" %> + <%= link_to "#{queue_label}: Bout #{queue_match.bout_number}", mat_path(@mat, bout_number: queue_match.bout_number), class: button_class %> + <% else %> + + <% end %> + <% end %> +
<% end %>

Bracket Position: <%= @match.bracket_position %>

diff --git a/app/views/mats/_current_match.html.erb b/app/views/mats/_current_match.html.erb index 77f3605..8aa9b2e 100644 --- a/app/views/mats/_current_match.html.erb +++ b/app/views/mats/_current_match.html.erb @@ -1,6 +1,8 @@ <% @mat = mat %> -<% @match = local_assigns[:match] || mat.queue1_match %> -<% @next_match = local_assigns[:next_match] || mat.queue2_match %> +<% @queue_matches = local_assigns[:queue_matches] || mat.queue_matches %> +<% @match = local_assigns[:match] || @queue_matches[0] %> +<% @match ||= @queue_matches[0] %> +<% @next_match = local_assigns[:next_match] || @queue_matches[1] %> <% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %> <% @wrestlers = [] %> diff --git a/test/controllers/mats_controller_test.rb b/test/controllers/mats_controller_test.rb index 2a6e8e5..1d3ca5f 100644 --- a/test/controllers/mats_controller_test.rb +++ b/test/controllers/mats_controller_test.rb @@ -259,6 +259,62 @@ class MatsControllerTest < ActionController::TestCase assert_match /#{bout_number}/, response.body, "The bout_number should be rendered on the page" end + test "mat show renders queue buttons for all four queue slots" do + sign_in_owner + + available_matches = @tournament.matches.where(mat_id: nil).limit(3).to_a + @mat.assign_match_to_queue!(available_matches[0], 2) if available_matches[0] + @mat.assign_match_to_queue!(available_matches[1], 3) if available_matches[1] + @mat.assign_match_to_queue!(available_matches[2], 4) if available_matches[2] + + get :show, params: { id: @mat.id } + assert_response :success + + assert_includes response.body, "Queue 1: Bout" + assert_includes response.body, "Queue 2:" + assert_includes response.body, "Queue 3:" + assert_includes response.body, "Queue 4:" + end + + test "mat show highlights selected queue button and keeps bout_number links working" do + sign_in_owner + + queue2_match = @mat.queue2_match + unless queue2_match + assignable = @tournament.matches.where(mat_id: nil).first + @mat.assign_match_to_queue!(assignable, 2) if assignable + queue2_match = @mat.reload.queue2_match + end + assert queue2_match, "Expected queue2 match to be present" + + get :show, params: { id: @mat.id, bout_number: queue2_match.bout_number, foo: "bar" } + assert_response :success + + assert_includes response.body, "Queue 2: Bout #{queue2_match.bout_number}" + assert_match(/btn btn-success btn-sm/, response.body) + assert_includes response.body, "bout_number=#{queue2_match.bout_number}" + end + + test "mat show falls back to queue1 when requested bout number is not currently queued" do + sign_in_owner + + queue1 = @mat.reload.queue1_match + assert queue1, "Expected queue1 to be present" + + get :show, params: { id: @mat.id, bout_number: 999999 } + assert_response :success + assert_includes response.body, "Bout #{queue1.bout_number}" + end + + test "mat show renders no matches assigned when queue is empty" do + sign_in_owner + + @mat.clear_queue! + get :show, params: { id: @mat.id } + assert_response :success + assert_includes response.body, "No matches assigned to this mat." + end + test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do sign_in_owner @@ -287,11 +343,12 @@ class MatsControllerTest < ActionController::TestCase end #TESTS THAT NEED MATCHES PUT ABOVE THIS - test "redirect show if no matches" do + test "show renders when no matches" do sign_in_owner wipe show - no_matches + success + assert_includes response.body, "No matches assigned to this mat." end # Assign Next Match Permissions diff --git a/test/controllers/up_matches_cache_test.rb b/test/controllers/up_matches_cache_test.rb index 7c80274..f48d341 100644 --- a/test/controllers/up_matches_cache_test.rb +++ b/test/controllers/up_matches_cache_test.rb @@ -50,6 +50,29 @@ class UpMatchesCacheTest < ActionController::TestCase assert_operator cache_writes(third_events), :>, 0, "Expected queue change to invalidate and rewrite at least one row fragment" end + test "up_matches row fragments hit cache after queue clear rewrite" do + first_events = cache_events_for_up_matches do + get :up_matches, params: { id: @tournament.id } + assert_response :success + end + assert_operator cache_writes(first_events), :>, 0, "Expected initial render to write row fragments" + + mat = @tournament.mats.first + clear_events = cache_events_for_up_matches do + mat.clear_queue! + get :up_matches, params: { id: @tournament.id } + assert_response :success + end + assert_operator cache_writes(clear_events), :>, 0, "Expected queue clear to invalidate and rewrite at least one row fragment" + + repeat_events = cache_events_for_up_matches do + get :up_matches, params: { id: @tournament.id } + assert_response :success + end + assert_equal 0, cache_writes(repeat_events), "Expected subsequent render after queue clear rewrite to reuse cached row fragments" + assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite" + end + private def cache_events_for_up_matches diff --git a/test/integration/mat_stats_live_updates_test.rb b/test/integration/mat_stats_live_updates_test.rb new file mode 100644 index 0000000..7c7b5d5 --- /dev/null +++ b/test/integration/mat_stats_live_updates_test.rb @@ -0,0 +1,37 @@ +require "test_helper" + +class MatStatsLiveUpdatesTest < ActionDispatch::IntegrationTest + include ActionView::RecordIdentifier + + test "destroying all matches broadcasts no-match state for mat stats stream" do + create_double_elim_tournament_single_weight_1_6(4) + mat = @tournament.mats.create!(name: "Mat 1") + @tournament.reset_and_fill_bout_board + stream = stream_name_for(mat) + + clear_streams(stream) + @tournament.destroy_all_matches + + assert_operator broadcasts_for(stream).size, :>, 0 + payload = broadcasts_for(stream).last + assert_includes payload, dom_id(mat, :current_match) + assert_includes payload, "No matches assigned to this mat." + end + + private + + def broadcasts_for(stream) + ActionCable.server.pubsub.broadcasts(stream) + end + + def clear_streams(*streams) + ActionCable.server.pubsub.clear + streams.each do |stream| + broadcasts_for(stream).clear + end + end + + def stream_name_for(streamable) + Turbo::StreamsChannel.send(:stream_name_from, [streamable]) + end +end diff --git a/test/models/match_broadcast_test.rb b/test/models/match_broadcast_test.rb index da51b34..5980a86 100644 --- a/test/models/match_broadcast_test.rb +++ b/test/models/match_broadcast_test.rb @@ -49,6 +49,38 @@ class MatchBroadcastTest < ActiveSupport::TestCase assert_includes broadcasts_for(stream).last, dom_id(mat, :current_match) end + test "destroy_all_matches clears mats and broadcasts no matches assigned" do + create_double_elim_tournament_single_weight_1_6(4) + mat = @tournament.mats.create!(name: "Mat 1") + @tournament.reset_and_fill_bout_board + stream = stream_name_for(mat) + + clear_streams(stream) + @tournament.destroy_all_matches + + assert_operator broadcasts_for(stream).size, :>, 0 + payload = broadcasts_for(stream).last + assert_includes payload, dom_id(mat, :current_match) + assert_includes payload, "No matches assigned to this mat." + assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids + end + + test "wipe tournament matches service clears mats and broadcasts no matches assigned" do + create_double_elim_tournament_single_weight_1_6(4) + mat = @tournament.mats.create!(name: "Mat 1") + @tournament.reset_and_fill_bout_board + stream = stream_name_for(mat) + + clear_streams(stream) + WipeTournamentMatches.new(@tournament).setUpMatchGeneration + + assert_operator broadcasts_for(stream).size, :>, 0 + payload = broadcasts_for(stream).last + assert_includes payload, dom_id(mat, :current_match) + assert_includes payload, "No matches assigned to this mat." + assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids + end + private def broadcasts_for(stream) diff --git a/test/models/up_matches_broadcast_test.rb b/test/models/up_matches_broadcast_test.rb index 1e3126b..5a95401 100644 --- a/test/models/up_matches_broadcast_test.rb +++ b/test/models/up_matches_broadcast_test.rb @@ -26,6 +26,21 @@ class UpMatchesBroadcastTest < ActiveSupport::TestCase assert_up_matches_replace_payload(broadcasts_for(stream).last) end + test "mat clear_queue broadcasts up matches board update" do + tournament = tournaments(:one) + mat = mats(:one) + match = matches(:tournament_1_bout_2000) + stream = stream_name_for(tournament) + + mat.update!(queue1: match.id) + clear_streams(stream) + + mat.clear_queue! + + assert_operator broadcasts_for(stream).size, :>, 0 + assert_up_matches_replace_payload(broadcasts_for(stream).last) + end + test "match mat assignment change broadcasts up matches board update" do tournament = tournaments(:one) mat = mats(:one) @@ -39,6 +54,21 @@ class UpMatchesBroadcastTest < ActiveSupport::TestCase assert_up_matches_replace_payload(broadcasts_for(stream).last) end + test "match mat unassignment broadcasts up matches board update" do + tournament = tournaments(:one) + mat = mats(:one) + match = matches(:tournament_1_bout_2001) + stream = stream_name_for(tournament) + + match.update!(mat_id: mat.id) + clear_streams(stream) + + match.update!(mat_id: nil) + + assert_operator broadcasts_for(stream).size, :>, 0 + assert_up_matches_replace_payload(broadcasts_for(stream).last) + end + test "mat update without queue slot changes does not broadcast up matches board update" do tournament = tournaments(:one) mat = mats(:one)