diff --git a/Gemfile b/Gemfile index 922fcdd39..eaa9263a0 100644 --- a/Gemfile +++ b/Gemfile @@ -174,6 +174,7 @@ group :development, :test do end group :test do + gem "action-cable-testing" gem "factory_bot_rails" gem "capybara", "~> 3.39.0" gem "poltergeist" diff --git a/Gemfile.lock b/Gemfile.lock index 9664bc64a..dfac496b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,8 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) + action-cable-testing (0.6.1) + actioncable (>= 5.0) actioncable (7.0.8.4) actionpack (= 7.0.8.4) activesupport (= 7.0.8.4) @@ -727,6 +729,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + action-cable-testing active_hash amoeba (= 3.0.0) binding_of_caller diff --git a/spec/channels/collaborators_channel_spec.rb b/spec/channels/collaborators_channel_spec.rb index 745809e02..879da9c3c 100644 --- a/spec/channels/collaborators_channel_spec.rb +++ b/spec/channels/collaborators_channel_spec.rb @@ -1,5 +1,141 @@ require "rails_helper" RSpec.describe CollaboratorsChannel, type: :channel do - pending "add some examples to (or delete) #{__FILE__}" + let(:channel_name) { "presence-chat-development-1-sep-step-consent-due-diligence" } + let(:user) { create(:user) } + let(:user_two) { create(:user) } + let(:tab_one) { "EiIKeLF" } + let(:tab_two) { "isdSJa2" } + let(:current_editor) { "#{user.id}:#{tab_one}:#{user.email}:#{user.full_name}:EDITOR" } + + before do + stub_connection + allow(Collaborators::BroadcastCollabWorker).to receive(:perform_async) + allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new) + end + + describe "#subscribed" do + context "when there are no subscribed users" do + before do + Rails.cache.write(channel_name, "") + end + + it "makes the first joining user an editor" do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}:#{tab_one}:#{user.email}:#{user.full_name}:EDITOR") + end + + it "broadcasts the collaborators for the room" do + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async) + .with(channel_name, "#{user.id}:#{tab_one}:#{user.email}:#{user.full_name}:EDITOR") + + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + end + end + + context "when there is already an editor for the channel" do + let(:second_user_tab) { "2392j123" } + before do + Rails.cache.write(channel_name, current_editor) + end + + it "adds the second user as a non-editor" do + subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s, "current_tab" => second_user_tab }) + + expect(Rails.cache.read(channel_name)).to eq("#{current_editor}/#{user_two.id}:#{second_user_tab}:#{user_two.email}:#{user_two.full_name}") + end + + it "broadcasts the collaborators for the room" do + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async) + .with(channel_name, "#{current_editor}/#{user_two.id}:#{second_user_tab}:#{user_two.email}:#{user_two.full_name}") + + subscribe({ "channel_name" => channel_name, "user_id" => user_two.id, "current_tab" => second_user_tab }) + end + end + + context "when the same user opens two tabs" do + it "adds the same user to the cache but as a non-editor" do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_two }) + + expect(Rails.cache.read(channel_name)).to eq("#{current_editor}/#{user.id}:#{tab_two}:#{user.email}:#{user.full_name}") + end + end + end + + describe "#unsubscribed" do + context "when the editor leaves the channel" do + before do + Rails.cache.write(channel_name, "") + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + end + + it "removes the user from the redis cache" do + expect(subscription).to be_confirmed + subscription.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq "" + end + + it "broadcasts the new collaborator list" do + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async) + .with(channel_name, "") + + subscription.unsubscribe_from_channel + end + end + + context "when the editor leaves the channel and another user is present" do + before do + Rails.cache.write(channel_name, "") + end + + it "makes the second user the new editor" do + sub_one = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s, "current_tab" => tab_two }) + + expect(Rails.cache.read(channel_name)).to eq("#{current_editor}/#{user_two.id}:#{tab_two}:#{user_two.email}:#{user_two.full_name}") + + sub_one.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq("#{user_two.id}:#{tab_two}:#{user_two.email}:#{user_two.full_name}:EDITOR") + end + end + + context "when a non-editor leaves the channel" do + it "keeps the editor the same as before" do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + sub_two = subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s, "current_tab" => tab_two }) + + sub_two.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq(current_editor) + end + end + + context "when one user has two tabs open" do + context "and they close the editor tab" do + it "makes the non-editing tab the editor" do + sub_one = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_two }) + + sub_one.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}:#{tab_two}:#{user.email}:#{user.full_name}:EDITOR") + end + end + + context "and they close the non-editor tab" do + it "keeps the editing tab as the editor" do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_one }) + sub_two = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s, "current_tab" => tab_two }) + + sub_two.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}:#{tab_one}:#{user.email}:#{user.full_name}:EDITOR") + end + end + end + end end diff --git a/spec/channels/general_room_channel_spec.rb b/spec/channels/general_room_channel_spec.rb new file mode 100644 index 000000000..022441556 --- /dev/null +++ b/spec/channels/general_room_channel_spec.rb @@ -0,0 +1,81 @@ +require "rails_helper" + +RSpec.describe GeneralRoomChannel, type: :channel do + let(:channel_name) { "presence-chat-development-1-general" } + let(:user) { create(:user) } + let(:user_two) { create(:user) } + + before do + stub_connection + allow(Collaborators::BroadcastCollabWorker).to receive(:perform_async) + allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new) + end + + describe "#subscribed" do + context "when the room is empty" do + it "adds the subscribing user ID to the redis cache" do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}") + end + + it "broadcasts the new room member" do + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async).with(channel_name, user.id.to_s) + + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + end + end + + context "when the room already has a member" do + before do + subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + end + + it "adds the subscribing user ID to the redis cache" do + subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s }) + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}/#{user_two.id}") + end + + it "broadcasts the new room member" do + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async).with(channel_name, "#{user.id.to_s}/#{user_two.id.to_s}") + + subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s }) + end + end + end + + describe "#unsubscribed" do + context "when there is one room member" do + it "clears the room" do + sub_one = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + + sub_one.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq "" + end + + it "broadcasts the new (empty) room members" do + sub_one = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async).with(channel_name, "") + sub_one.unsubscribe_from_channel + end + end + + context "when there are multiple room members" do + it "removes only the unsubscribing room member" do + sub_one = subscribe({ "channel_name" => channel_name, "user_id" => user.id.to_s }) + sub_two = subscribe({ "channel_name" => channel_name, "user_id" => user_two.id.to_s }) + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}/#{user_two.id}") + + expect(Collaborators::BroadcastCollabWorker).to receive(:perform_async).with(channel_name, user.id.to_s) + + sub_two.unsubscribe_from_channel + + expect(Rails.cache.read(channel_name)).to eq("#{user.id}") + end + end + end +end