FEATURE: introduces group channels (#24288)

Group channels will allow users to create channels with a name and invite people. It's possible to add people even after creation of the channel. Removing users is not yet possible but will be added in the near future.

Technically a group channel is `direct_message_channel` with a group attribute set to true on its direct message (chatable). This model might evolve in the future but offers much flexibility for now without having to rely on a complex migration.

The commit essentially consists of:
- a migration to set existing direct message channels with more than 2 users to a group
- a new message creator which allows to search, add members, and create groups
- a new `AddUsersToChannel` service
- a modified `SearchChatable` service
This commit is contained in:
Joffrey JAFFEUX
2023-11-10 11:29:28 +01:00
committed by GitHub
parent a8d6dc4d3a
commit ab832cc865
121 changed files with 2969 additions and 2332 deletions

View File

@ -33,9 +33,13 @@ Fabricator(:private_category_channel, from: :category_channel) do
end
Fabricator(:direct_message_channel, from: :chat_channel) do
transient :users, following: true, with_membership: true
transient :users, :group, following: true, with_membership: true
chatable do |attrs|
Fabricate(:direct_message, users: attrs[:users] || [Fabricate(:user), Fabricate(:user)])
Fabricate(
:direct_message,
users: attrs[:users] || [Fabricate(:user), Fabricate(:user)],
group: attrs[:group] || false,
)
end
status { :open }
name nil

View File

@ -46,9 +46,39 @@ RSpec.describe Chat::GuardianExtensions do
expect(staff_guardian.can_create_chat_channel?).to eq(true)
end
it "only staff can edit chat channels" do
expect(guardian.can_edit_chat_channel?).to eq(false)
expect(staff_guardian.can_edit_chat_channel?).to eq(true)
context "when category channel" do
it "allows staff to edit chat channels" do
expect(guardian.can_edit_chat_channel?(channel)).to eq(false)
expect(staff_guardian.can_edit_chat_channel?(channel)).to eq(true)
end
end
context "when direct message channel" do
context "when member of channel" do
context "when group" do
before do
dm_channel.chatable.update!(group: true)
add_users_to_channel(user, dm_channel)
end
it "allows to edit the channel" do
expect(user.guardian.can_edit_chat_channel?(dm_channel)).to eq(true)
end
end
context "when not group" do
it "doesn’t allow to edit the channel" do
Chat::DirectMessageUser.create(user: user, direct_message: dm_channel.chatable)
expect(user.guardian.can_edit_chat_channel?(dm_channel)).to eq(false)
end
end
end
context "when not member of channel" do
it "doesn’t allow to edit the channel" do
expect(user.guardian.can_edit_chat_channel?(dm_channel)).to eq(false)
end
end
end
it "only staff can close chat channels" do

View File

@ -102,6 +102,17 @@ module ChatSpecHelpers
service_failed!(result) if result.failure?
result
end
def add_users_to_channel(users, channel, user: Discourse.system_user)
result =
::Chat::AddUsersToChannel.call(
guardian: user.guardian,
channel_id: channel.id,
usernames: Array(users).map(&:username),
)
service_failed!(result) if result.failure?
result
end
end
RSpec.configure do |config|

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
RSpec.describe Chat::Api::ChannelsMembershipsController do
fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) do
Fabricate(:direct_message_channel, group: true, users: [current_user, Fabricate(:user)])
end
before do
SiteSetting.chat_enabled = true
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
channel_1.add(current_user)
sign_in(current_user)
end
describe "#create" do
describe "success" do
it "works" do
add_users_to_channel(current_user, channel_1)
post "/chat/api/channels/#{channel_1.id}/memberships",
params: {
usernames: [Fabricate(:user).username],
}
expect(response.status).to eq(200)
end
end
context "when users can't be added" do
before { channel_1.chatable.update(group: false) }
it "returns a 422" do
post "/chat/api/channels/#{channel_1.id}/memberships",
params: {
usernames: [Fabricate(:user).username],
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"].first).to eq(
I18n.t("chat.errors.users_cant_be_added_to_channel"),
)
end
end
context "when channel is not found" do
before { channel_1.chatable.update!(group: false) }
it "returns a 404" do
get "/chat/api/channels/-999/messages", params: { usernames: [Fabricate(:user).username] }
expect(response.status).to eq(404)
end
end
end
end

View File

@ -14,13 +14,16 @@ RSpec.describe Chat::Api::ChatablesController do
describe "without chat permissions" do
it "errors errors for anon" do
get "/chat/api/chatables"
expect(response.status).to eq(403)
end
it "errors when user cannot chat" do
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff]
sign_in(current_user)
get "/chat/api/chatables"
expect(response.status).to eq(403)
end
end
@ -28,9 +31,11 @@ RSpec.describe Chat::Api::ChatablesController do
describe "with chat permissions" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before { sign_in(current_user) }
before { channel_1.add(current_user) }
it "returns results" do
sign_in(current_user)
get "/chat/api/chatables", params: { term: channel_1.name }
expect(response.status).to eq(200)

View File

@ -23,7 +23,10 @@ RSpec.describe Chat::Api::DirectMessagesController do
describe "#create" do
before { Group.refresh_automatic_groups! }
shared_examples "creating dms" do
describe "dm with one other user" do
let(:usernames) { user1.username }
let(:direct_message_user_ids) { [current_user.id, user1.id] }
it "creates a new dm channel with username(s) provided" do
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
@ -35,31 +38,55 @@ RSpec.describe Chat::Api::DirectMessagesController do
it "returns existing dm channel if one exists for username(s)" do
create_dm_channel(direct_message_user_ids)
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
}.not_to change { Chat::DirectMessage.count }
end
end
describe "dm with one other user" do
let(:usernames) { user1.username }
let(:direct_message_user_ids) { [current_user.id, user1.id] }
include_examples "creating dms"
end
describe "dm with myself" do
let(:usernames) { [current_user.username] }
let(:direct_message_user_ids) { [current_user.id] }
include_examples "creating dms"
it "creates a new dm channel with username(s) provided" do
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
}.to change { Chat::DirectMessage.count }.by(1)
expect(Chat::DirectMessage.last.direct_message_users.map(&:user_id)).to match_array(
direct_message_user_ids,
)
end
it "returns existing dm channel if one exists for username(s)" do
create_dm_channel(direct_message_user_ids)
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
}.not_to change { Chat::DirectMessage.count }
end
end
describe "dm with two other users" do
let(:usernames) { [user1, user2, user3].map(&:username) }
let(:direct_message_user_ids) { [current_user.id, user1.id, user2.id, user3.id] }
include_examples "creating dms"
it "creates a new dm channel with username(s) provided" do
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
}.to change { Chat::DirectMessage.count }.by(1)
expect(Chat::DirectMessage.last.direct_message_users.map(&:user_id)).to match_array(
direct_message_user_ids,
)
end
it "createsa new dm channel" do
create_dm_channel(direct_message_user_ids)
expect {
post "/chat/api/direct-message-channels.json", params: { target_usernames: [usernames] }
}.to change { Chat::DirectMessage.count }.by(1)
end
end
it "creates Chat::UserChatChannelMembership records" do

View File

@ -611,6 +611,7 @@ RSpec.describe Chat::ChatController do
expect(response.status).to eq(403)
Chat::DirectMessageUser.create(user: user, direct_message: dm_channel.chatable)
expect {
post "/chat/drafts.json", params: { chat_channel_id: dm_channel.id, data: "{}" }
}.to change { Chat::Draft.count }.by(1)

View File

@ -0,0 +1,104 @@
# frozen_string_literal: true
RSpec.describe Chat::AddUsersToChannel do
describe described_class::Contract, type: :model do
subject(:contract) { described_class.new(usernames: [], channel_id: nil) }
it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :usernames }
end
describe ".call" do
subject(:result) { described_class.call(params) }
fab!(:current_user) { Fabricate(:user) }
fab!(:users) { Fabricate.times(5, :user) }
fab!(:direct_message) { Fabricate(:direct_message, users: [current_user], group: true) }
fab!(:channel) { Fabricate(:direct_message_channel, chatable: direct_message) }
let(:guardian) { Guardian.new(current_user) }
let(:params) do
{ guardian: guardian, channel_id: channel.id, usernames: users.map(&:username) }
end
context "when all steps pass" do
before { channel.add(current_user) }
it "fetches users to add" do
expect(result.users.map(&:username)).to contain_exactly(*users.map(&:username))
end
it "doesn't include existing direct message users" do
Chat::DirectMessageUser.create!(user: users.first, direct_message: direct_message)
expect(result.users.map(&:username)).to contain_exactly(*users[1..-1].map(&:username))
end
it "creates memberships" do
expect { result }.to change { channel.user_chat_channel_memberships.count }.by(
users.count + 1,
) # +1 for system user creating the notice message and added to the channel
end
it "creates direct messages users" do
expect { result }.to change { ::Chat::DirectMessageUser.count }.by(users.count + 1) # +1 for system user creating the notice message and added to the channel
end
it "updates users count" do
expect { result }.to change { channel.reload.user_count }.by(users.count + 1) # +1 for system user creating the notice message and added to the channel
end
it "creates a chat message to show added users" do
added_users = result.users
channel.chat_messages.last.tap do |message|
expect(message.message).to eq(
I18n.t(
"chat.channel.users_invited_to_channel",
invited_users: added_users.map { |u| "@#{u.username}" }.join(", "),
inviting_user: "@#{current_user.username}",
count: added_users.count,
),
)
expect(message.user).to eq(Discourse.system_user)
end
end
end
context "when there are too many usernames" do
before { SiteSetting.chat_max_direct_message_users = 2 }
it { is_expected.to fail_a_contract }
end
context "when channel is not found" do
before { params[:channel_id] = -999 }
it { is_expected.to fail_to_find_a_model(:channel) }
end
context "when user don't have access to channel" do
fab!(:channel) { Fabricate(:private_category_channel, group: Fabricate(:group)) }
it { is_expected.to fail_a_policy(:can_add_users_to_channel) }
end
context "when channel is not a group" do
before { direct_message.update!(group: false) }
it { is_expected.to fail_a_policy(:can_add_users_to_channel) }
end
context "when channel is not a direct message channel" do
fab!(:channel) { Fabricate(:chat_channel) }
it { is_expected.to fail_a_policy(:can_add_users_to_channel) }
end
context "when user is not admin and not a member of the channel" do
fab!(:current_user) { Fabricate(:user) }
it { is_expected.to fail_a_policy(:can_add_users_to_channel) }
end
end
end

View File

@ -26,7 +26,9 @@ RSpec.describe Chat::CreateDirectMessageChannel do
fab!(:user_2) { Fabricate(:user, username: "elaine") }
let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, target_usernames: %w[lechuck elaine] } }
let(:target_usernames) { [user_1.username, user_2.username] }
let(:name) { "" }
let(:params) { { guardian: guardian, target_usernames: target_usernames, name: name } }
before { Group.refresh_automatic_groups! }
@ -35,6 +37,10 @@ RSpec.describe Chat::CreateDirectMessageChannel do
expect(result).to be_a_success
end
it "updates user count" do
expect(result.channel.user_count).to eq(3) # current user + user_1 + user_2
end
it "creates the channel" do
expect { result }.to change { Chat::Channel.count }
expect(result.channel.chatable).to have_attributes(
@ -58,16 +64,59 @@ RSpec.describe Chat::CreateDirectMessageChannel do
end
context "when there is an existing direct message channel for the target users" do
before { described_class.call(params) }
context "when a name has been given" do
let(:target_usernames) { [user_1.username] }
let(:name) { "Monkey Island" }
it "does not create a channel" do
expect { result }.to not_change { Chat::Channel.count }.and not_change {
Chat::DirectMessage.count
}
it "creates a second channel" do
described_class.call(params)
expect { result }.to change { Chat::Channel.count }.and change {
Chat::DirectMessage.count
}
end
end
it "does not double-insert the channel memberships" do
expect { result }.not_to change { Chat::UserChatChannelMembership.count }
context "when the channel has more than one user" do
let(:target_usernames) { [user_1.username, user_2.username] }
it "creates a second channel" do
described_class.call(params)
expect { result }.to change { Chat::Channel.count }.and change {
Chat::DirectMessage.count
}
end
end
context "when the channel has one user and no name" do
let(:target_usernames) { [user_1.username] }
it "reuses the existing channel" do
existing_channel = described_class.call(params).channel
expect(result.channel.id).to eq(existing_channel.id)
end
end
context "when theres also a group channel with same users" do
let(:target_usernames) { [user_1.username] }
it "returns the non group existing channel" do
group_channel = described_class.call(params.merge(name: "cats")).channel
channel = described_class.call(params).channel
expect(result.channel.id).to_not eq(group_channel.id)
expect(result.channel.id).to eq(channel.id)
end
end
end
context "when a name is given" do
let(:name) { "Monkey Island" }
it "sets it as the channel name" do
expect(result.channel.name).to eq(name)
end
end
end

View File

@ -7,20 +7,34 @@ RSpec.describe Chat::SearchChatable do
fab!(:current_user) { Fabricate(:user, username: "bob-user") }
fab!(:sam) { Fabricate(:user, username: "sam-user") }
fab!(:charlie) { Fabricate(:user, username: "charlie-user") }
fab!(:alain) { Fabricate(:user, username: "alain-user") }
fab!(:channel_1) { Fabricate(:chat_channel, name: "bob-channel") }
fab!(:channel_2) { Fabricate(:direct_message_channel, users: [current_user, sam]) }
fab!(:channel_3) { Fabricate(:direct_message_channel, users: [current_user, sam, charlie]) }
fab!(:channel_4) { Fabricate(:direct_message_channel, users: [sam, charlie]) }
fab!(:channel_5) { Fabricate(:direct_message_channel, users: [current_user, charlie, alain]) }
let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, term: term } }
let(:term) { "" }
let(:include_users) { false }
let(:include_category_channels) { false }
let(:include_direct_message_channels) { false }
let(:excluded_memberships_channel_id) { nil }
let(:params) do
{
guardian: guardian,
term: term,
include_users: include_users,
include_category_channels: include_category_channels,
include_direct_message_channels: include_direct_message_channels,
excluded_memberships_channel_id: excluded_memberships_channel_id,
}
end
before do
SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone]
# simpler user search without having to worry about user search data
SiteSetting.enable_names = false
return unless guardian.can_create_direct_message?
channel_1.add(current_user)
end
@ -29,118 +43,144 @@ RSpec.describe Chat::SearchChatable do
expect(result).to be_a_success
end
it "returns chatables" do
it "cleans the term" do
params[:term] = "#bob"
expect(result.term).to eq("bob")
params[:term] = "@bob"
expect(result.term).to eq("bob")
end
it "fetches user memberships" do
expect(result.memberships).to contain_exactly(
channel_1.membership_for(current_user),
channel_2.membership_for(current_user),
channel_3.membership_for(current_user),
channel_5.membership_for(current_user),
)
expect(result.category_channels).to contain_exactly(channel_1)
expect(result.direct_message_channels).to contain_exactly(channel_2, channel_3)
expect(result.users).to include(current_user, sam)
end
it "doesn’t return direct message of other users" do
expect(result.direct_message_channels).to_not include(channel_4)
end
context "when including users" do
let(:include_users) { true }
context "with private channel" do
fab!(:private_channel_1) { Fabricate(:private_category_channel, name: "private") }
let(:term) { "#private" }
it "fetches users" do
expect(result.users).to include(current_user, sam, charlie, alain)
end
it "doesn’t return category channels you can't access" do
expect(result.category_channels).to_not include(private_channel_1)
it "can filter usernames" do
params[:term] = "sam"
expect(result.users).to contain_exactly(sam)
end
it "can filter users with a membership to a specific channel" do
params[:excluded_memberships_channel_id] = channel_1.id
expect(result.users).to_not include(current_user)
end
end
context "when public channels are disabled" do
it "does not return category channels" do
SiteSetting.enable_public_channels = false
context "when not including users" do
let(:include_users) { false }
expect(described_class.call(params).category_channels).to be_blank
it "doesn’t fetch users" do
expect(result.users).to be_nil
end
end
end
context "when term is prefixed with #" do
let(:term) { "#" }
context "when including category channels" do
let(:include_category_channels) { true }
it "doesn’t return users" do
expect(result.users).to be_blank
expect(result.category_channels).to contain_exactly(channel_1)
expect(result.direct_message_channels).to contain_exactly(channel_2, channel_3)
end
end
it "fetches category channels" do
expect(result.category_channels).to include(channel_1)
end
context "when term is prefixed with @" do
let(:term) { "@" }
it "can filter titles" do
searched_channel = Fabricate(:chat_channel, name: "beaver")
params[:term] = "beaver"
it "doesn’t return channels" do
expect(result.users).to include(current_user, sam)
expect(result.category_channels).to be_blank
expect(result.direct_message_channels).to be_blank
end
end
expect(result.category_channels).to contain_exactly(searched_channel)
end
context "when filtering" do
context "with full match" do
let(:term) { "bob" }
it "can filter slugs" do
searched_channel = Fabricate(:chat_channel, name: "beaver", slug: "something")
params[:term] = "something"
expect(result.category_channels).to contain_exactly(searched_channel)
end
it "doesn’t include category channels you can't access" do
Fabricate(:private_category_channel)
it "returns matching channels" do
expect(result.users).to contain_exactly(current_user)
expect(result.category_channels).to contain_exactly(channel_1)
expect(result.direct_message_channels).to contain_exactly(channel_2, channel_3)
end
end
context "with partial match" do
let(:term) { "cha" }
context "when not including category channels" do
let(:include_category_channels) { false }
it "returns matching channels" do
expect(result.users).to contain_exactly(charlie)
expect(result.category_channels).to contain_exactly(channel_1)
expect(result.direct_message_channels).to contain_exactly(channel_3)
it "doesn’t fetch category channels" do
expect(result.category_channels).to be_nil
end
end
end
context "when filtering with non existing term" do
let(:term) { "xxxxxxxxxx" }
context "when including direct message channels" do
let(:include_direct_message_channels) { true }
it "returns matching channels" do
expect(result.users).to be_blank
expect(result.category_channels).to be_blank
expect(result.direct_message_channels).to be_blank
it "fetches direct message channels" do
expect(result.direct_message_channels).to contain_exactly(channel_2, channel_3, channel_5)
end
it "doesn’t fetches inaccessible direct message channels" do
expect(result.direct_message_channels).to_not include(channel_4)
end
it "can filter by title" do
searched_channel =
Fabricate(:direct_message_channel, users: [current_user, sam, charlie], name: "koala")
params[:term] = "koala"
expect(result.direct_message_channels).to contain_exactly(searched_channel)
end
it "can filter by slug" do
searched_channel =
Fabricate(
:direct_message_channel,
users: [current_user, sam, charlie],
slug: "capybara",
)
params[:term] = "capybara"
expect(result.direct_message_channels).to contain_exactly(searched_channel)
end
it "can filter by users" do
cedric = Fabricate(:user, username: "cedric")
searched_channel =
Fabricate(:direct_message_channel, users: [current_user, cedric], slug: "capybara")
searched_channel.add(cedric)
params[:term] = "cedric"
expect(result.direct_message_channels).to contain_exactly(searched_channel)
end
context "when also includes users" do
let(:include_users) { true }
it "excludes one to one direct message channels with user" do
expect(result.users).to include(sam)
expect(result.direct_message_channels).to contain_exactly(channel_3, channel_5)
end
end
end
end
context "when filtering with @prefix" do
let(:term) { "@bob" }
context "when not including direct message channels" do
let(:include_direct_message_channels) { false }
it "returns matching channels" do
expect(result.users).to contain_exactly(current_user)
expect(result.category_channels).to be_blank
expect(result.direct_message_channels).to be_blank
end
end
context "when filtering with #prefix" do
let(:term) { "#bob" }
it "returns matching channels" do
expect(result.users).to be_blank
expect(result.category_channels).to contain_exactly(channel_1)
expect(result.direct_message_channels).to contain_exactly(channel_2, channel_3)
end
end
context "when current user can't created direct messages" do
let(:term) { "@bob" }
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:staff] }
it "doesn’t return users" do
expect(result.users).to be_blank
it "doesn’t fetch direct message channels" do
expect(result.direct_message_channels).to be_nil
end
end
end
end

View File

@ -23,12 +23,6 @@ RSpec.describe Chat::UpdateChannel do
it { is_expected.to fail_a_policy(:check_channel_permission) }
end
context "when the user tries to edit a DM channel" do
fab!(:channel) { Fabricate(:direct_message_channel, users: [current_user, Fabricate(:user)]) }
it { is_expected.to fail_a_policy(:no_direct_message_channel) }
end
context "when channel is a category one" do
context "when a valid user provides valid params" do
let(:message) do

View File

@ -15,7 +15,6 @@
"muted": { "type": "boolean" },
"desktop_notification_level": { "type": "string" },
"mobile_notification_level": { "type": "string" },
"last_viewed_at": { "type": "string" },
"following": { "type": "boolean" },
"user": {
"type": ["object", "null"],
@ -24,7 +23,9 @@
"id": { "type": "number" },
"name": { "type": "string" },
"avatar_template": { "type": "string" },
"username": { "type": "string" }
"username": { "type": "string" },
"can_chat": { "type": "boolean" },
"has_chat_enabled": { "type": "boolean" }
}
},
"last_viewed_at": { "type": "datetime" }

View File

@ -26,7 +26,7 @@ RSpec.describe "Channel - Info - Members page", type: :system do
it "redirects to settings page" do
chat_page.visit_channel_members(channel_1)
expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/settings")
expect(page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}/info/members")
end
end
@ -64,4 +64,42 @@ RSpec.describe "Channel - Info - Members page", type: :system do
end
end
end
context "when category channel" do
it "doesn’t allow to add members" do
chat_page.visit_channel_members(channel_1)
expect(chat_page).to have_no_css(".chat-channel-members__list-item.-add-member")
end
end
context "when category channel" do
fab!(:channel_1) do
Fabricate(
:direct_message_channel,
slug: "test-channel",
users: [current_user, Fabricate(:user), Fabricate(:user)],
group: true,
)
end
it "allows to add members" do
new_user = Fabricate(:user)
chat_page.visit_channel_members(channel_1)
chat_page.find(".chat-channel-members__list-item.-add-member").click
chat_page.find(".chat-message-creator__members-input").fill_in(with: new_user.username)
chat_page.find(".chat-message-creator__list-item").click
chat_page.find(".add-to-channel").click
expect(chat_page).to have_current_path("/chat/c/#{channel_1.slug}/#{channel_1.id}")
expect(chat_page).to have_content(
I18n.t(
"chat.channel.users_invited_to_channel",
invited_users: "@#{new_user.username}",
inviting_user: "@#{current_user.username}",
count: 1,
),
)
end
end
end

View File

@ -159,6 +159,8 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
context "as staff" do
fab!(:current_user) { Fabricate(:admin) }
before { channel_1.add(current_user) }
it "can edit name" do
chat_page.visit_channel_settings(channel_1)
@ -170,7 +172,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
edit_modal.fill_and_save_name(name)
expect(channel_settings_page).to have_name(name)
expect(page).to have_content(name)
end
it "can edit description" do
@ -191,6 +193,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
it "can edit slug" do
chat_page.visit_channel_settings(channel_1)
edit_modal = channel_settings_page.open_edit_modal
slug = "gonzo-slug"
@ -199,7 +202,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
edit_modal.fill_and_save_slug(slug)
expect(channel_settings_page).to have_slug(slug)
expect(page).to have_current_path("/chat/c/gonzo-slug/#{channel_1.id}")
end
it "can clear the slug to use the autogenerated version based on the name" do
@ -213,7 +216,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
edit_modal.wait_for_auto_generated_slug
edit_modal.save_changes
expect(channel_settings_page).to have_slug("test-channel")
expect(page).to have_current_path("/chat/c/test-channel/#{channel_1.id}")
end
it "shows settings page" do

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
RSpec.describe "Flag message", type: :system do
let(:chat_page) { PageObjects::Pages::Chat.new }
fab!(:current_user) { Fabricate(:user) }
before do
chat_system_bootstrap
sign_in(current_user)
end
it "lists preloaded channels by default" do
channel_1 = Fabricate(:chat_channel)
channel_1.add(current_user)
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_listing(channel_1)
end
it "can filter channels" do
channel_1 = Fabricate(:chat_channel)
channel_2 = Fabricate(:chat_channel)
channel_1.add(current_user)
channel_2.add(current_user)
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter(channel_2.title)
expect(chat_page.message_creator).to be_listing(channel_2)
expect(chat_page.message_creator).to be_not_listing(channel_1)
end
it "can filter users" do
user_1 = Fabricate(:user)
user_2 = Fabricate(:user)
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter(user_2.username)
expect(chat_page.message_creator).to be_listing(user_2)
expect(chat_page.message_creator).to be_not_listing(user_1)
end
it "can filter direct message channels" do
channel_1 = Fabricate(:direct_message_channel, users: [current_user])
channel_2 =
Fabricate(
:direct_message_channel,
users: [current_user, Fabricate(:user), Fabricate(:user, username: "user_1")],
)
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter("user_1")
expect(chat_page.message_creator).to be_listing(channel_2)
expect(chat_page.message_creator).to be_not_listing(channel_1)
end
it "can create a new group message" do
user_1 = Fabricate(:user)
user_2 = Fabricate(:user)
visit("/")
chat_page.prefers_full_page
chat_page.open_new_message
chat_page.find("#new-group-chat").click
chat_page.find(".chat-message-creator__new-group-header__input").fill_in(with: "cats")
chat_page.find(".chat-message-creator__members-input").fill_in(with: user_1.username)
chat_page.message_creator.click_row(user_1)
chat_page.find(".chat-message-creator__members-input").fill_in(with: user_2.username)
chat_page.message_creator.click_row(user_2)
page.find(".create-chat-group").click
expect(page).to have_current_path(%r{/chat/c/cats/\d+})
end
end

View File

@ -24,7 +24,7 @@ RSpec.describe "Drawer", type: :system do
drawer_page.open_channel(channel)
page.find(".chat-channel-title").click
expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}/info/settings")
expect(page).to have_current_path("/chat/c/#{channel.slug}/#{channel.id}/info/members")
end
end
end

View File

@ -1,470 +0,0 @@
# frozen_string_literal: true
RSpec.describe "New message", type: :system do
fab!(:current_user) { Fabricate(:admin) }
let(:chat_page) { PageObjects::Pages::Chat.new }
before do
# simpler user search without having to worry about user search data
SiteSetting.enable_names = false
chat_system_bootstrap
sign_in(current_user)
end
it "cmd + k opens new message" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_opened
end
context "when public channels are disabled" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before do
SiteSetting.enable_public_channels = false
channel_1.add(current_user)
end
it "doesn’t list public channels" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_not_listing(channel_1)
end
it "has a correct placeholder" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator.input["placeholder"]).to eq(
I18n.t("js.chat.new_message_modal.default_user_search_placeholder"),
)
end
end
context "when selecting more users than allowed" do
fab!(:current_user) { Fabricate(:trust_level_1) }
fab!(:user_1) { Fabricate(:user) }
fab!(:user_2) { Fabricate(:user) }
before { SiteSetting.chat_max_direct_message_users = 1 }
it "shows an error" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter(user_1.username)
chat_page.message_creator.shift_click_row(user_1)
chat_page.message_creator.filter(user_2.username)
chat_page.message_creator.shift_click_row(user_2)
chat_page.message_creator.click_cta
expect(page).to have_content(
I18n.t(
"chat.errors.over_chat_max_direct_message_users",
count: SiteSetting.chat_max_direct_message_users,
),
)
end
end
context "when public channels are disabled and user can't create direct message" do
fab!(:current_user) { Fabricate(:user) }
before do
SiteSetting.enable_public_channels = false
SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:staff]
end
it "doesn’t list public channels" do
visit("/")
chat_page.open_new_message(ensure_open: false)
expect(chat_page.message_creator).to be_closed
end
end
context "when the content is not filtered" do
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:channel_2) { Fabricate(:chat_channel) }
fab!(:user_1) { Fabricate(:user) }
fab!(:user_2) { Fabricate(:user) }
fab!(:direct_message_channel_1) do
Fabricate(:direct_message_channel, users: [current_user, user_1])
end
fab!(:direct_message_channel_2) { Fabricate(:direct_message_channel, users: [user_1, user_2]) }
before { channel_1.add(current_user) }
it "lists channels the user is following" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
expect(chat_page.message_creator).to be_not_listing(direct_message_channel_2)
expect(chat_page.message_creator).to be_listing(user_1)
expect(chat_page.message_creator).to be_not_listing(user_2)
end
end
context "with no selection" do
context "with unread state" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:channel_2) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
before do
channel_1.add(user_1)
channel_1.add(current_user)
Fabricate(:chat_message, chat_channel: channel_1, user: user_1)
Fabricate(:chat_message, chat_channel: channel_2, user: user_1)
end
it "shows the correct state" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to have_unread_row(channel_1, urgent: false)
expect(chat_page.message_creator).to have_unread_row(user_1, urgent: true)
end
end
context "when clicking a row" do
context "when the row is a channel" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before { channel_1.add(current_user) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.click_row(channel_1)
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
context "when the row is a user" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.click_row(user_1)
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
end
context "when shift clicking a row" do
context "when the row is a channel" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before { channel_1.add(current_user) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.shift_click_row(channel_1)
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
context "when the row is a user" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
it "adds the user" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.shift_click_row(user_1)
expect(chat_page.message_creator).to be_selecting(user_1)
end
end
end
context "when pressing enter" do
context "when the row is a channel" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before { channel_1.add(current_user) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.click_row(channel_1)
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
context "when the row is a user" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.click_row(user_1)
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
end
context "when pressing shift+enter" do
context "when the row is a channel" do
fab!(:channel_1) { Fabricate(:chat_channel) }
before { channel_1.add(current_user) }
it "opens the channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.shift_enter_shortcut
expect(chat_page).to have_drawer(channel_id: channel_1.id)
end
end
context "when the row is a user" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
it "adds the user" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.shift_enter_shortcut
expect(chat_page.message_creator).to be_selecting(user_1)
end
end
end
context "when navigating content with arrows" do
fab!(:channel_1) { Fabricate(:chat_channel, name: "channela") }
fab!(:channel_2) { Fabricate(:chat_channel, name: "channelb") }
before do
channel_1.add(current_user)
channel_2.add(current_user)
end
it "changes active content" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_listing(channel_1, active: true)
chat_page.message_creator.arrow_down_shortcut
expect(chat_page.message_creator).to be_listing(channel_2, active: true)
chat_page.message_creator.arrow_down_shortcut
expect(chat_page.message_creator).to be_listing(channel_1, active: true)
chat_page.message_creator.arrow_up_shortcut
expect(chat_page.message_creator).to be_listing(channel_2, active: true)
end
end
context "with disabled content" do
fab!(:user_1) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
before { user_1.user_option.update!(chat_enabled: false) }
it "doesn’t make the content active" do
visit("/")
chat_page.open_new_message
expect(chat_page.message_creator).to be_listing(user_1, inactive: true, disabled: true)
end
end
end
context "when filtering" do
fab!(:channel_1) { Fabricate(:chat_channel, name: "bob-channel") }
fab!(:user_1) { Fabricate(:user, username: "bob-user") }
fab!(:user_2) { Fabricate(:user) }
fab!(:channel_2) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
fab!(:channel_3) { Fabricate(:direct_message_channel, users: [current_user, user_1, user_2]) }
before { channel_1.add(current_user) }
context "when query is the name of the category" do
fab!(:category) { Fabricate(:category, name: "dev") }
fab!(:channel_1) { Fabricate(:category_channel, chatable: category, name: "something dev") }
fab!(:channel_2) { Fabricate(:category_channel, chatable: category, name: "something else") }
it "favors the channel name" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter("dev")
expect(chat_page.message_creator).to be_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
end
end
context "with no prefix" do
it "lists all matching content" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter("bob")
expect(chat_page.message_creator).to be_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
expect(chat_page.message_creator).to be_listing(channel_3)
expect(chat_page.message_creator).to be_listing(user_1)
expect(chat_page.message_creator).to be_not_listing(user_2)
end
end
context "with channel prefix" do
it "lists matching channel" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter("#bob")
expect(chat_page.message_creator).to be_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
expect(chat_page.message_creator).to be_listing(channel_3)
expect(chat_page.message_creator).to be_not_listing(user_1)
expect(chat_page.message_creator).to be_not_listing(user_2)
end
end
context "with user prefix" do
it "lists matching users" do
visit("/")
chat_page.open_new_message
chat_page.message_creator.filter("@bob")
expect(chat_page.message_creator).to be_not_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
expect(chat_page.message_creator).to be_not_listing(channel_3)
expect(chat_page.message_creator).to be_listing(user_1)
expect(chat_page.message_creator).to be_not_listing(user_2)
end
end
end
context "with selection" do
fab!(:channel_1) { Fabricate(:chat_channel, name: "bob-channel") }
fab!(:user_1) { Fabricate(:user, username: "bob-user") }
fab!(:user_2) { Fabricate(:user, username: "bobby-user") }
fab!(:user_3) { Fabricate(:user, username: "sam-user") }
fab!(:channel_2) { Fabricate(:direct_message_channel, users: [current_user, user_1]) }
fab!(:channel_3) { Fabricate(:direct_message_channel, users: [current_user, user_2]) }
before do
channel_1.add(current_user)
visit("/")
chat_page.open_new_message
chat_page.message_creator.shift_click_row(user_1)
end
context "when pressing enter" do
it "opens the channel" do
chat_page.message_creator.enter_shortcut
expect(chat_page).to have_drawer(channel_id: channel_2.id)
end
end
context "when clicking cta" do
it "opens the channel" do
chat_page.message_creator.click_cta
expect(chat_page).to have_drawer(channel_id: channel_2.id)
end
end
context "when filtering" do
it "shows only matching users regardless of prefix" do
chat_page.message_creator.filter("#bob")
expect(chat_page.message_creator).to be_listing(user_1)
expect(chat_page.message_creator).to be_listing(user_2)
expect(chat_page.message_creator).to be_not_listing(user_3)
expect(chat_page.message_creator).to be_not_listing(channel_1)
expect(chat_page.message_creator).to be_not_listing(channel_2)
expect(chat_page.message_creator).to be_not_listing(channel_3)
end
it "shows selected user as selected in content" do
chat_page.message_creator.filter("@bob")
expect(chat_page.message_creator).to be_listing(user_1, selected: true)
expect(chat_page.message_creator).to be_listing(user_2, selected: false)
end
end
context "when clicking another user" do
it "adds it to the selection" do
chat_page.message_creator.filter("@bob")
chat_page.message_creator.click_row(user_2)
expect(chat_page.message_creator).to be_selecting(user_1)
expect(chat_page.message_creator).to be_selecting(user_2)
end
end
context "when pressing backspace" do
it "removes it" do
chat_page.message_creator.backspace_shortcut
expect(chat_page.message_creator).to be_selecting(user_1, active: true)
chat_page.message_creator.backspace_shortcut
expect(chat_page.message_creator).to be_not_selecting(user_1)
end
end
context "when navigating selection with arrow left/right" do
it "changes active item" do
chat_page.message_creator.filter("@bob")
chat_page.message_creator.click_row(user_2)
chat_page.message_creator.arrow_left_shortcut
expect(chat_page.message_creator).to be_selecting(user_2, active: true)
chat_page.message_creator.arrow_left_shortcut
expect(chat_page.message_creator).to be_selecting(user_1, active: true)
chat_page.message_creator.arrow_left_shortcut
expect(chat_page.message_creator).to be_selecting(user_2, active: true)
chat_page.message_creator.arrow_right_shortcut
expect(chat_page.message_creator).to be_selecting(user_1, active: true)
end
end
context "when clicking selection" do
it "removes it" do
chat_page.message_creator.click_item(user_1)
expect(chat_page.message_creator).to be_not_selecting(user_1)
end
end
end
end

View File

@ -13,7 +13,7 @@ module PageObjects
end
def input
component.find(".chat-message-creator__input")
component.find(".chat-message-creator__search-input__input")
end
def filter(query = "")
@ -104,7 +104,7 @@ module PageObjects
end
def build_row_selector(chatable, **args)
selector = ".chat-message-creator__row"
selector = ".chat-message-creator__list-item"
selector += content_selector(**args)
selector += chatable_selector(chatable, **args)
selector
@ -116,9 +116,9 @@ module PageObjects
selector = ".-selected" if args[:selected]
selector = ":not(.-disabled)" if args[:enabled]
if args[:active]
selector += ".-active"
selector += ".-highlighted"
elsif args[:inactive]
selector += ":not(.-active)"
selector += ":not(.-highlighted)"
end
selector
end
@ -126,14 +126,11 @@ module PageObjects
def chatable_selector(chatable, **args)
selector = ""
if chatable.try(:category_channel?)
selector += ".-channel"
selector += "[data-id='c-#{chatable.id}']"
selector += "[data-identifier='c-#{chatable.id}']"
elsif chatable.try(:direct_message_channel?)
selector += ".-channel"
selector += "[data-id='c-#{chatable.id}']"
selector += "[data-identifier='c-#{chatable.id}']"
else
selector += ".-user"
selector += "[data-id='u-#{chatable.id}']"
selector += "[data-identifier='u-#{chatable.id}']"
end
selector
end

View File

@ -16,8 +16,6 @@ RSpec.describe "User status | sidebar", type: :system do
end
it "shows user status" do
Jobs.run_immediately!
visit("/")
expect(find(".user-status-message .emoji")["alt"]).to eq("heart")
@ -26,8 +24,6 @@ RSpec.describe "User status | sidebar", type: :system do
context "when changing status" do
it "updates status" do
Jobs.run_immediately!
visit("/")
current_user.set_status!("offline", "tooth")
@ -38,8 +34,6 @@ RSpec.describe "User status | sidebar", type: :system do
context "when removing status" do
it "removes status" do
Jobs.run_immediately!
visit("/")
current_user.clear_status!