mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 11:53:31 +08:00
For mentions within threads, the mentioned user can experience a stuck notification. This is due to thread memberships only being created for users who interact with a thread. Without the membership we cannot track if the message containing the mention was read by the user. The solution to this explored in this PR is: - auto add memberships for mentioned users (only direct mentions for performance reasons). - update channel/thread unread queries to check notification read status AND thread membership last read message id when counting mentions. Previously the mention count would remain until the user notification (containing the mention) was read. However this only happens if the user clicks the notification or clicks dismiss all notifications. When a user navigated to the thread without clicking the notification, the green/urgent badge on chat would remain even after a hard page refresh.
373 lines
11 KiB
Ruby
Vendored
373 lines
11 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
describe Chat::ChannelUnreadsQuery do
|
|
subject(:query) do
|
|
described_class.call(
|
|
channel_ids: channel_ids,
|
|
user_id: current_user.id,
|
|
include_missing_memberships: include_missing_memberships,
|
|
include_read: include_read,
|
|
).map(&:to_h)
|
|
end
|
|
|
|
fab!(:channel_1) { Fabricate(:category_channel) }
|
|
fab!(:current_user) { Fabricate(:user) }
|
|
let(:include_missing_memberships) { false }
|
|
let(:include_read) { true }
|
|
let(:channel_ids) { [channel_1.id] }
|
|
|
|
before do
|
|
SiteSetting.chat_enabled = true
|
|
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
|
channel_1.add(current_user)
|
|
end
|
|
|
|
context "with unread message" do
|
|
before { Fabricate(:chat_message, chat_channel: channel_1) }
|
|
|
|
it "returns a correct unread count" do
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
channel_id: channel_1.id,
|
|
watched_threads_unread_count: 0,
|
|
},
|
|
)
|
|
end
|
|
|
|
context "when the membership has been muted" do
|
|
before do
|
|
channel_1
|
|
.user_chat_channel_memberships
|
|
.find_by(user_id: current_user.id)
|
|
.update!(muted: true)
|
|
end
|
|
|
|
it "returns a zeroed unread count" do
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
watched_threads_unread_count: 0,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
|
|
context "for unread messages in a thread" do
|
|
fab!(:thread_om) { Fabricate(:chat_message, chat_channel: channel_1) }
|
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: thread_om) }
|
|
|
|
it "does include the original message in the unread count" do
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 2,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
it "does not include other thread messages in the unread count" do
|
|
Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
|
Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 2,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
|
|
context "for multiple channels" do
|
|
fab!(:channel_2) { Fabricate(:category_channel) }
|
|
let(:channel_ids) { [channel_1.id, channel_2.id] }
|
|
|
|
before do
|
|
channel_2.add(current_user)
|
|
Fabricate(:chat_message, chat_channel: channel_2)
|
|
Fabricate(:chat_message, chat_channel: channel_2)
|
|
end
|
|
|
|
it "returns accurate counts" do
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 2,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_2.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
|
|
context "for channels where the user has no membership" do
|
|
before do
|
|
current_user
|
|
.user_chat_channel_memberships
|
|
.where(chat_channel_id: channel_2.id)
|
|
.destroy_all
|
|
end
|
|
|
|
it "does not return counts for the channels" do
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
|
|
context "when include_missing_memberships is true" do
|
|
let(:include_missing_memberships) { true }
|
|
|
|
it "does return zeroed counts for the channels" do
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_2.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
|
|
context "when include_read is false" do
|
|
let(:include_read) { false }
|
|
|
|
it "does not return counts for the channels" do
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with unread mentions" do
|
|
before { Jobs.run_immediately! }
|
|
|
|
def create_mention(message, channel)
|
|
notification =
|
|
Notification.create!(
|
|
notification_type: Notification.types[:chat_mention],
|
|
user_id: current_user.id,
|
|
data: { chat_message_id: message.id, chat_channel_id: channel.id }.to_json,
|
|
)
|
|
Chat::UserMention.create!(
|
|
notifications: [notification],
|
|
user: current_user,
|
|
chat_message: message,
|
|
)
|
|
end
|
|
|
|
it "returns a correct unread mention" do
|
|
message = Fabricate(:chat_message, chat_channel: channel_1)
|
|
create_mention(message, channel_1)
|
|
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 1,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
context "for unread mentions in a thread" do
|
|
fab!(:thread_om) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
|
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: thread_om) }
|
|
|
|
it "does include the original message in the mention count" do
|
|
create_mention(thread_om, channel_1)
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 1,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
it "includes thread messages with mentions in the channel mention count" do
|
|
thread_message_1 = Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
|
thread_message_2 = Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
|
create_mention(thread_message_1, channel_1)
|
|
create_mention(thread_message_2, channel_1)
|
|
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 2,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
|
|
context "for multiple channels" do
|
|
fab!(:channel_2) { Fabricate(:category_channel) }
|
|
let(:channel_ids) { [channel_1.id, channel_2.id] }
|
|
|
|
it "returns accurate counts" do
|
|
message = Fabricate(:chat_message, chat_channel: channel_1)
|
|
create_mention(message, channel_1)
|
|
|
|
channel_2.add(current_user)
|
|
Fabricate(:chat_message, chat_channel: channel_2)
|
|
message_2 = Fabricate(:chat_message, chat_channel: channel_2)
|
|
create_mention(message_2, channel_2)
|
|
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 1,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
{
|
|
mention_count: 1,
|
|
unread_count: 2,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_2.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with watched threads" do
|
|
fab!(:message) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
|
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: message) }
|
|
fab!(:thread_reply) { Fabricate(:chat_message, chat_channel: channel_1, thread: thread) }
|
|
|
|
before do
|
|
channel_1.update(threading_enabled: true)
|
|
channel_1.membership_for(current_user).mark_read!(message.id)
|
|
thread.membership_for(current_user).update!(
|
|
notification_level: ::Chat::NotificationLevels.all[:watching],
|
|
)
|
|
end
|
|
|
|
it "returns correct watched thread unread count" do
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
watched_threads_unread_count: 1,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
it "returns unread and watched thread unread counts" do
|
|
Fabricate(:chat_message, chat_channel: channel_1)
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 1,
|
|
watched_threads_unread_count: 1,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
context "for multiple channels" do
|
|
fab!(:channel_2) { Fabricate(:category_channel) }
|
|
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_2, user: current_user) }
|
|
fab!(:thread_2) { Fabricate(:chat_thread, channel: channel_2, original_message: message_2) }
|
|
let(:channel_ids) { [channel_1.id, channel_2.id] }
|
|
|
|
before do
|
|
channel_2.add(current_user)
|
|
channel_2.update(threading_enabled: true)
|
|
channel_2.membership_for(current_user).mark_read!(message_2.id)
|
|
thread_2.membership_for(current_user).update!(
|
|
notification_level: ::Chat::NotificationLevels.all[:watching],
|
|
)
|
|
|
|
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_2)
|
|
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_2)
|
|
end
|
|
|
|
it "returns accurate counts" do
|
|
expect(query).to match_array(
|
|
[
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
watched_threads_unread_count: 1,
|
|
channel_id: channel_1.id,
|
|
},
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
watched_threads_unread_count: 2,
|
|
channel_id: channel_2.id,
|
|
},
|
|
],
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with nothing unread" do
|
|
it "returns a correct state" do
|
|
expect(query.first).to eq(
|
|
{
|
|
mention_count: 0,
|
|
unread_count: 0,
|
|
watched_threads_unread_count: 0,
|
|
channel_id: channel_1.id,
|
|
},
|
|
)
|
|
end
|
|
|
|
context "when include_read is false" do
|
|
let(:include_read) { false }
|
|
|
|
it "returns nothing" do
|
|
expect(query).to eq([])
|
|
end
|
|
end
|
|
end
|
|
end
|