mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 07:53:49 +08:00
FEATURE: Add user status to inline mentions in chat (#20564)
This PR adds status to mentions in chat and makes those mentions receive live updates. There are known unfinished part in this implementation: when posting a message, status on mentions on that message appears immediately, but only if a user used autocomplete when typing the message. If user copy and paste a message with mentions into chat composer, those mentions won't have user status on them. PRs with fixes for both problems are following soon. Preparations for this PR that were made previously include: - DEV: correct a relationship – a chat message may have several mentions 0dcfd7ddeccd438fed97c15827214a3ddd944838 - DEV: extract the logic for extracting and expanding mentions from ChatNotifier 75b81b6854087842b53b4c9559ef5836d9516269 - DEV: Always create chat mention records fa543cda06594b5bebe0ab35e48e613540f9d3dc - DEV: better split create_notification! and send_notifications logic e292c45924bb0b0a79497e5f494afcb8af2e1efc - DEV: more tests for mentions when updating chat messages e7292e1682bb910635af26879c31a17b8843b738 - DEV: extract updating status on mentions into a lib function e49d338c21885189970e51a60ac79ab8c1fc397e - DEV: Create and update chat message mentions earlier 35a414bb38b2ab990ff920f8c3adf2f877249f12 - DEV: Create a chat_mention record when self mentioning 2703f2311aefd87a7774316b50f5339626ea5b48 - DEV: When deleting a chat message, do not delete mention records f4fde4e49b03d5e8f01d02e2354c553c82f3402d
This commit is contained in:

committed by
GitHub

parent
436b68a581
commit
d4a5b79592
@ -1436,6 +1436,10 @@ User.reopen(Evented, {
|
|||||||
this._subscribersCount--;
|
this._subscribersCount--;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isTrackingStatus() {
|
||||||
|
return this._subscribersCount > 0;
|
||||||
|
},
|
||||||
|
|
||||||
_statusChanged(sender, key) {
|
_statusChanged(sender, key) {
|
||||||
this.trigger("status-changed");
|
this.trigger("status-changed");
|
||||||
|
|
||||||
|
@ -39,6 +39,10 @@ export function success() {
|
|||||||
return response({ success: true });
|
return response({ success: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function OK(resp = {}, headers = {}) {
|
||||||
|
return [200, headers, resp];
|
||||||
|
}
|
||||||
|
|
||||||
const loggedIn = () => !!User.current();
|
const loggedIn = () => !!User.current();
|
||||||
const helpers = { response, success, parsePostData };
|
const helpers = { response, success, parsePostData };
|
||||||
|
|
||||||
|
@ -416,6 +416,7 @@ module Chat
|
|||||||
.includes(:uploads)
|
.includes(:uploads)
|
||||||
.includes(chat_channel: :chatable)
|
.includes(chat_channel: :chatable)
|
||||||
.includes(:thread)
|
.includes(:thread)
|
||||||
|
.includes(:chat_mentions)
|
||||||
|
|
||||||
query = query.includes(user: :user_status) if SiteSetting.enable_user_status
|
query = query.includes(user: :user_status) if SiteSetting.enable_user_status
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ module Chat
|
|||||||
.includes(:uploads)
|
.includes(:uploads)
|
||||||
.includes(chat_channel: :chatable)
|
.includes(chat_channel: :chatable)
|
||||||
.includes(:thread)
|
.includes(:thread)
|
||||||
|
.includes(:chat_mentions)
|
||||||
.where(chat_channel_id: channel.id)
|
.where(chat_channel_id: channel.id)
|
||||||
|
|
||||||
query = query.includes(user: :user_status) if SiteSetting.enable_user_status
|
query = query.includes(user: :user_status) if SiteSetting.enable_user_status
|
||||||
|
@ -18,13 +18,21 @@ module Chat
|
|||||||
:thread_id,
|
:thread_id,
|
||||||
:thread_reply_count,
|
:thread_reply_count,
|
||||||
:thread_title,
|
:thread_title,
|
||||||
:chat_channel_id
|
:chat_channel_id,
|
||||||
|
:mentioned_users
|
||||||
|
|
||||||
has_one :user, serializer: Chat::MessageUserSerializer, embed: :objects
|
has_one :user, serializer: Chat::MessageUserSerializer, embed: :objects
|
||||||
has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects
|
has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects
|
||||||
has_one :in_reply_to, serializer: Chat::InReplyToSerializer, embed: :objects
|
has_one :in_reply_to, serializer: Chat::InReplyToSerializer, embed: :objects
|
||||||
has_many :uploads, serializer: ::UploadSerializer, embed: :objects
|
has_many :uploads, serializer: ::UploadSerializer, embed: :objects
|
||||||
|
|
||||||
|
def mentioned_users
|
||||||
|
User
|
||||||
|
.where(id: object.chat_mentions.pluck(:user_id))
|
||||||
|
.map { |user| BasicUserWithStatusSerializer.new(user, root: false) }
|
||||||
|
.as_json
|
||||||
|
end
|
||||||
|
|
||||||
def channel
|
def channel
|
||||||
@channel ||= @options.dig(:chat_channel) || object.chat_channel
|
@channel ||= @options.dig(:chat_channel) || object.chat_channel
|
||||||
end
|
end
|
||||||
|
@ -19,6 +19,7 @@ import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete";
|
|||||||
import { isEmpty, isPresent } from "@ember/utils";
|
import { isEmpty, isPresent } from "@ember/utils";
|
||||||
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
|
import User from "discourse/models/user";
|
||||||
|
|
||||||
export default class ChatComposer extends Component {
|
export default class ChatComposer extends Component {
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
@ -419,7 +420,11 @@ export default class ChatComposer extends Component {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
treatAsTextarea: true,
|
treatAsTextarea: true,
|
||||||
autoSelectFirstSuggestion: true,
|
autoSelectFirstSuggestion: true,
|
||||||
transformComplete: (v) => v.username || v.name,
|
transformComplete: (userData) => {
|
||||||
|
const user = User.create(userData);
|
||||||
|
this.currentMessage.mentionedUsers.set(user.id, user);
|
||||||
|
return user.username || user.name;
|
||||||
|
},
|
||||||
dataSource: (term) => {
|
dataSource: (term) => {
|
||||||
return userSearch({ term, includeGroups: true }).then((result) => {
|
return userSearch({ term, includeGroups: true }).then((result) => {
|
||||||
if (result?.users?.length > 0) {
|
if (result?.users?.length > 0) {
|
||||||
|
@ -60,6 +60,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div
|
<div
|
||||||
|
{{did-insert this.refreshStatusOnMentions}}
|
||||||
|
{{did-update this.refreshStatusOnMentions @message.version}}
|
||||||
|
{{did-update this.initMentionedUsers @message.version}}
|
||||||
class={{concat-class
|
class={{concat-class
|
||||||
"chat-message"
|
"chat-message"
|
||||||
(if @message.staged "chat-message-staged" "chat-message-persisted")
|
(if @message.staged "chat-message-staged" "chat-message-persisted")
|
||||||
|
@ -12,6 +12,7 @@ import { getOwner } from "discourse-common/lib/get-owner";
|
|||||||
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
|
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
|
||||||
|
|
||||||
let _chatMessageDecorators = [];
|
let _chatMessageDecorators = [];
|
||||||
|
|
||||||
@ -43,6 +44,11 @@ export default class ChatMessage extends Component {
|
|||||||
|
|
||||||
@optionalService adminTools;
|
@optionalService adminTools;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.initMentionedUsers();
|
||||||
|
}
|
||||||
|
|
||||||
get pane() {
|
get pane() {
|
||||||
return this.args.context === MESSAGE_CONTEXT_THREAD
|
return this.args.context === MESSAGE_CONTEXT_THREAD
|
||||||
? this.chatChannelThreadPane
|
? this.chatChannelThreadPane
|
||||||
@ -104,7 +110,7 @@ export default class ChatMessage extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.args.message.expanded = true;
|
this.args.message.expanded = true;
|
||||||
|
this.refreshStatusOnMentions();
|
||||||
recursiveExpand(this.args.message);
|
recursiveExpand(this.args.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +126,27 @@ export default class ChatMessage extends Component {
|
|||||||
@action
|
@action
|
||||||
teardownChatMessage() {
|
teardownChatMessage() {
|
||||||
cancel(this._invitationSentTimer);
|
cancel(this._invitationSentTimer);
|
||||||
|
this.#teardownMentionedUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
refreshStatusOnMentions() {
|
||||||
|
schedule("afterRender", () => {
|
||||||
|
if (!this.messageContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.args.message.mentionedUsers.forEach((user) => {
|
||||||
|
const href = `/u/${user.username.toLowerCase()}`;
|
||||||
|
const mentions = this.messageContainer.querySelectorAll(
|
||||||
|
`a.mention[href="${href}"]`
|
||||||
|
);
|
||||||
|
|
||||||
|
mentions.forEach((mention) => {
|
||||||
|
updateUserStatusOnMention(mention, user.status, this.currentUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -135,6 +162,18 @@ export default class ChatMessage extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
initMentionedUsers() {
|
||||||
|
this.args.message.mentionedUsers.forEach((user) => {
|
||||||
|
if (user.isTrackingStatus()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.trackStatus();
|
||||||
|
user.on("status-changed", this, "refreshStatusOnMentions");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get messageContainer() {
|
get messageContainer() {
|
||||||
const id = this.args.message?.id;
|
const id = this.args.message?.id;
|
||||||
if (id) {
|
if (id) {
|
||||||
@ -406,4 +445,11 @@ export default class ChatMessage extends Component {
|
|||||||
dismissMentionWarning() {
|
dismissMentionWarning() {
|
||||||
this.args.message.mentionWarning = null;
|
this.args.message.mentionWarning = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#teardownMentionedUsers() {
|
||||||
|
this.args.message.mentionedUsers.forEach((user) => {
|
||||||
|
user.stopTrackingStatus();
|
||||||
|
user.off("status-changed", this, "refreshStatusOnMentions");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ export default class ChatMessage {
|
|||||||
this.uploads = new TrackedArray(args.uploads || []);
|
this.uploads = new TrackedArray(args.uploads || []);
|
||||||
this.user = this.#initUserModel(args.user);
|
this.user = this.#initUserModel(args.user);
|
||||||
this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null;
|
this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null;
|
||||||
|
this.mentionedUsers = this.#initMentionedUsers(args.mentioned_users);
|
||||||
}
|
}
|
||||||
|
|
||||||
duplicate() {
|
duplicate() {
|
||||||
@ -311,6 +312,17 @@ export default class ChatMessage {
|
|||||||
return reactions.map((reaction) => ChatMessageReaction.create(reaction));
|
return reactions.map((reaction) => ChatMessageReaction.create(reaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#initMentionedUsers(mentionedUsers) {
|
||||||
|
const map = new Map();
|
||||||
|
if (mentionedUsers) {
|
||||||
|
mentionedUsers.forEach((userData) => {
|
||||||
|
const user = User.create(userData);
|
||||||
|
map.set(user.id, user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
#initUserModel(user) {
|
#initUserModel(user) {
|
||||||
if (!user || user instanceof User) {
|
if (!user || user instanceof User) {
|
||||||
return user;
|
return user;
|
||||||
|
@ -66,8 +66,10 @@ export default class ChatRoute extends DiscourseRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deactivate(transition) {
|
deactivate(transition) {
|
||||||
const url = this.router.urlFor(transition.from.name);
|
if (transition) {
|
||||||
this.chatStateManager.storeChatURL(url);
|
const url = this.router.urlFor(transition.from.name);
|
||||||
|
this.chatStateManager.storeChatURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
this.chat.activeChannel = null;
|
this.chat.activeChannel = null;
|
||||||
this.chat.updatePresence();
|
this.chat.updatePresence();
|
||||||
|
@ -136,6 +136,19 @@ describe Chat::MessageCreator do
|
|||||||
expect(events.map { _1[:event_name] }).to include(:chat_message_created)
|
expect(events.map { _1[:event_name] }).to include(:chat_message_created)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "publishes created message to message bus" do
|
||||||
|
content = "a test chat message"
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.create(chat_channel: public_chat_channel, user: user1, content: content)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["message"]).to eq(content)
|
||||||
|
expect(message["chat_message"]["user"]["id"]).to eq(user1.id)
|
||||||
|
end
|
||||||
|
|
||||||
context "with mentions" do
|
context "with mentions" do
|
||||||
it "creates mentions and mention notifications for public chat" do
|
it "creates mentions and mention notifications for public chat" do
|
||||||
message =
|
message =
|
||||||
@ -405,6 +418,52 @@ describe Chat::MessageCreator do
|
|||||||
mention = user2.chat_mentions.where(chat_message: message).first
|
mention = user2.chat_mentions.where(chat_message: message).first
|
||||||
expect(mention.notification).to be_nil
|
expect(mention.notification).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "adds mentioned user and their status to the message bus message" do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
status = { description: "dentist", emoji: "tooth" }
|
||||||
|
user2.set_status!(status[:description], status[:emoji])
|
||||||
|
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.create(
|
||||||
|
chat_channel: public_chat_channel,
|
||||||
|
user: user1,
|
||||||
|
content: "Hey @#{user2.username}",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["mentioned_users"].count).to be(1)
|
||||||
|
mentioned_user = message["chat_message"]["mentioned_users"][0]
|
||||||
|
|
||||||
|
expect(mentioned_user["id"]).to eq(user2.id)
|
||||||
|
expect(mentioned_user["username"]).to eq(user2.username)
|
||||||
|
expect(mentioned_user["status"]).to be_present
|
||||||
|
expect(mentioned_user["status"].slice(:description, :emoji)).to eq(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't add mentioned user's status to the message bus message when status is disabled" do
|
||||||
|
SiteSetting.enable_user_status = false
|
||||||
|
user2.set_status!("dentist", "tooth")
|
||||||
|
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.create(
|
||||||
|
chat_channel: public_chat_channel,
|
||||||
|
user: user1,
|
||||||
|
content: "Hey @#{user2.username}",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["mentioned_users"].count).to be(1)
|
||||||
|
mentioned_user = message["chat_message"]["mentioned_users"][0]
|
||||||
|
|
||||||
|
expect(mentioned_user["status"]).to be_blank
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a chat_mention record without notification when self mentioning" do
|
it "creates a chat_mention record without notification when self mentioning" do
|
||||||
|
@ -124,6 +124,23 @@ describe Chat::MessageUpdater do
|
|||||||
expect(events.map { _1[:event_name] }).to include(:chat_message_edited)
|
expect(events.map { _1[:event_name] }).to include(:chat_message_edited)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "publishes updated message to message bus" do
|
||||||
|
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
|
||||||
|
new_content = "New content"
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.update(
|
||||||
|
guardian: guardian,
|
||||||
|
chat_message: chat_message,
|
||||||
|
new_content: new_content,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["message"]).to eq(new_content)
|
||||||
|
end
|
||||||
|
|
||||||
context "with mentions" do
|
context "with mentions" do
|
||||||
it "sends notifications if a message was updated with new mentions" do
|
it "sends notifications if a message was updated with new mentions" do
|
||||||
message = create_chat_message(user1, "Mentioning @#{user2.username}", public_chat_channel)
|
message = create_chat_message(user1, "Mentioning @#{user2.username}", public_chat_channel)
|
||||||
@ -228,6 +245,56 @@ describe Chat::MessageUpdater do
|
|||||||
expect(mention.notification).to be_nil
|
expect(mention.notification).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "adds mentioned user and their status to the message bus message" do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
status = { description: "dentist", emoji: "tooth" }
|
||||||
|
user2.set_status!(status[:description], status[:emoji])
|
||||||
|
chat_message = create_chat_message(user1, "This will be updated", public_chat_channel)
|
||||||
|
new_content = "Hey @#{user2.username}"
|
||||||
|
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.update(
|
||||||
|
guardian: guardian,
|
||||||
|
chat_message: chat_message,
|
||||||
|
new_content: new_content,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["mentioned_users"].count).to be(1)
|
||||||
|
mentioned_user = message["chat_message"]["mentioned_users"][0]
|
||||||
|
|
||||||
|
expect(mentioned_user["id"]).to eq(user2.id)
|
||||||
|
expect(mentioned_user["username"]).to eq(user2.username)
|
||||||
|
expect(mentioned_user["status"]).to be_present
|
||||||
|
expect(mentioned_user["status"].slice(:description, :emoji)).to eq(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't add mentioned user's status to the message bus message when status is disabled" do
|
||||||
|
SiteSetting.enable_user_status = false
|
||||||
|
user2.set_status!("dentist", "tooth")
|
||||||
|
chat_message = create_chat_message(user1, "This will be updated", public_chat_channel)
|
||||||
|
new_content = "Hey @#{user2.username}"
|
||||||
|
|
||||||
|
messages =
|
||||||
|
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
|
||||||
|
described_class.update(
|
||||||
|
guardian: guardian,
|
||||||
|
chat_message: chat_message,
|
||||||
|
new_content: new_content,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to be(1)
|
||||||
|
message = messages[0].data
|
||||||
|
expect(message["chat_message"]["mentioned_users"].count).to be(1)
|
||||||
|
mentioned_user = message["chat_message"]["mentioned_users"][0]
|
||||||
|
|
||||||
|
expect(mentioned_user["status"]).to be_blank
|
||||||
|
end
|
||||||
|
|
||||||
context "when updating a mentioned user" do
|
context "when updating a mentioned user" do
|
||||||
it "updates the mention record" do
|
it "updates the mention record" do
|
||||||
chat_message = create_chat_message(user1, "ping @#{user2.username}", public_chat_channel)
|
chat_message = create_chat_message(user1, "ping @#{user2.username}", public_chat_channel)
|
||||||
|
@ -145,6 +145,60 @@ RSpec.describe Chat::ChatController do
|
|||||||
expect(response.parsed_body["meta"]["channel_message_bus_last_id"]).not_to eq(nil)
|
expect(response.parsed_body["meta"]["channel_message_bus_last_id"]).not_to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with mentions" do
|
||||||
|
it "returns mentioned users" do
|
||||||
|
last_message = chat_channel.chat_messages.last
|
||||||
|
user1 = Fabricate(:user)
|
||||||
|
user2 = Fabricate(:user)
|
||||||
|
Fabricate(:chat_mention, user: user1, chat_message: last_message)
|
||||||
|
Fabricate(:chat_mention, user: user2, chat_message: last_message)
|
||||||
|
|
||||||
|
get "/chat/#{chat_channel.id}/messages.json", params: { page_size: page_size }
|
||||||
|
|
||||||
|
mentioned_users = response.parsed_body["chat_messages"].last["mentioned_users"]
|
||||||
|
expect(mentioned_users[0]["id"]).to eq(user1.id)
|
||||||
|
expect(mentioned_users[0]["username"]).to eq(user1.username)
|
||||||
|
expect(mentioned_users[1]["id"]).to eq(user2.id)
|
||||||
|
expect(mentioned_users[1]["username"]).to eq(user2.username)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an empty list if no one was mentioned" do
|
||||||
|
get "/chat/#{chat_channel.id}/messages.json", params: { page_size: page_size }
|
||||||
|
|
||||||
|
last_message = response.parsed_body["chat_messages"].last
|
||||||
|
expect(last_message).to have_key("mentioned_users")
|
||||||
|
expect(last_message["mentioned_users"]).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with user status" do
|
||||||
|
fab!(:status) { Fabricate(:user_status) }
|
||||||
|
fab!(:user1) { Fabricate(:user, user_status: status) }
|
||||||
|
fab!(:chat_mention) do
|
||||||
|
Fabricate(:chat_mention, user: user1, chat_message: chat_channel.chat_messages.last)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns status if enabled in settings" do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
|
||||||
|
get "/chat/#{chat_channel.id}/messages.json", params: { page_size: page_size }
|
||||||
|
|
||||||
|
mentioned_user = response.parsed_body["chat_messages"].last["mentioned_users"][0]
|
||||||
|
expect(mentioned_user).to have_key("status")
|
||||||
|
expect(mentioned_user["status"]["emoji"]).to eq(status.emoji)
|
||||||
|
expect(mentioned_user["status"]["description"]).to eq(status.description)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't return status if disabled in settings" do
|
||||||
|
SiteSetting.enable_user_status = false
|
||||||
|
|
||||||
|
get "/chat/#{chat_channel.id}/messages.json", params: { page_size: page_size }
|
||||||
|
|
||||||
|
mentioned_user = response.parsed_body["chat_messages"].last["mentioned_users"][0]
|
||||||
|
expect(mentioned_user).not_to have_key("status")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "scrolling to the past" do
|
describe "scrolling to the past" do
|
||||||
it "returns the correct messages in created_at, id order" do
|
it "returns the correct messages in created_at, id order" do
|
||||||
get "/chat/#{chat_channel.id}/messages.json",
|
get "/chat/#{chat_channel.id}/messages.json",
|
||||||
|
@ -133,16 +133,18 @@ RSpec.describe "Chat channel", type: :system, js: true do
|
|||||||
|
|
||||||
context "when a message contains mentions" do
|
context "when a message contains mentions" do
|
||||||
fab!(:other_user) { Fabricate(:user) }
|
fab!(:other_user) { Fabricate(:user) }
|
||||||
|
fab!(:message) do
|
||||||
before do
|
|
||||||
channel_1.add(other_user)
|
|
||||||
channel_1.add(current_user)
|
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:chat_message,
|
:chat_message,
|
||||||
chat_channel: channel_1,
|
chat_channel: channel_1,
|
||||||
message: "hello @here @all @#{current_user.username} @#{other_user.username} @unexisting",
|
message: "hello @here @all @#{current_user.username} @#{other_user.username} @unexisting",
|
||||||
user: other_user,
|
user: other_user,
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
channel_1.add(other_user)
|
||||||
|
channel_1.add(current_user)
|
||||||
sign_in(current_user)
|
sign_in(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -158,6 +160,23 @@ RSpec.describe "Chat channel", type: :system, js: true do
|
|||||||
expect(page).to have_selector(".mention", text: "@#{other_user.username}")
|
expect(page).to have_selector(".mention", text: "@#{other_user.username}")
|
||||||
expect(page).to have_selector(".mention", text: "@unexisting")
|
expect(page).to have_selector(".mention", text: "@unexisting")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "renders user status on mentions" do
|
||||||
|
SiteSetting.enable_user_status = true
|
||||||
|
current_user.set_status!("off to dentist", "tooth")
|
||||||
|
other_user.set_status!("surfing", "surfing_man")
|
||||||
|
Fabricate(:chat_mention, user: current_user, chat_message: message)
|
||||||
|
Fabricate(:chat_mention, user: other_user, chat_message: message)
|
||||||
|
|
||||||
|
chat.visit_channel(channel_1)
|
||||||
|
|
||||||
|
expect(page).to have_selector(
|
||||||
|
".mention .user-status[title='#{current_user.user_status.description}']",
|
||||||
|
)
|
||||||
|
expect(page).to have_selector(
|
||||||
|
".mention .user-status[title='#{other_user.user_status.description}']",
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when reply is right under" do
|
context "when reply is right under" do
|
||||||
|
@ -0,0 +1,351 @@
|
|||||||
|
import {
|
||||||
|
acceptance,
|
||||||
|
emulateAutocomplete,
|
||||||
|
loggedInUser,
|
||||||
|
publishToMessageBus,
|
||||||
|
query,
|
||||||
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
import { test } from "qunit";
|
||||||
|
import {
|
||||||
|
click,
|
||||||
|
triggerEvent,
|
||||||
|
triggerKeyEvent,
|
||||||
|
visit,
|
||||||
|
waitFor,
|
||||||
|
} from "@ember/test-helpers";
|
||||||
|
import pretender, { OK } from "discourse/tests/helpers/create-pretender";
|
||||||
|
|
||||||
|
acceptance("Chat | User status on mentions", function (needs) {
|
||||||
|
const channelId = 1;
|
||||||
|
const messageId = 1;
|
||||||
|
const actingUser = {
|
||||||
|
id: 1,
|
||||||
|
username: "acting_user",
|
||||||
|
};
|
||||||
|
const mentionedUser1 = {
|
||||||
|
id: 1000,
|
||||||
|
username: "user1",
|
||||||
|
status: {
|
||||||
|
description: "surfing",
|
||||||
|
emoji: "surfing_man",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mentionedUser2 = {
|
||||||
|
id: 2000,
|
||||||
|
username: "user2",
|
||||||
|
status: {
|
||||||
|
description: "vacation",
|
||||||
|
emoji: "desert_island",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mentionedUser3 = {
|
||||||
|
id: 3000,
|
||||||
|
username: "user3",
|
||||||
|
status: {
|
||||||
|
description: "off to dentist",
|
||||||
|
emoji: "tooth",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const message = {
|
||||||
|
id: messageId,
|
||||||
|
message: `Hey @${mentionedUser1.username}`,
|
||||||
|
cooked: `<p>Hey <a class="mention" href="/u/${mentionedUser1.username}">@${mentionedUser1.username}</a></p>`,
|
||||||
|
mentioned_users: [mentionedUser1],
|
||||||
|
user: actingUser,
|
||||||
|
};
|
||||||
|
const newStatus = {
|
||||||
|
description: "working remotely",
|
||||||
|
emoji: "house",
|
||||||
|
};
|
||||||
|
const channel = {
|
||||||
|
id: channelId,
|
||||||
|
chatable_id: 1,
|
||||||
|
chatable_type: "Category",
|
||||||
|
meta: { message_bus_last_ids: {} },
|
||||||
|
current_user_membership: { following: true },
|
||||||
|
chatable: { id: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
needs.settings({ chat_enabled: true });
|
||||||
|
|
||||||
|
needs.user({
|
||||||
|
...actingUser,
|
||||||
|
has_chat_enabled: true,
|
||||||
|
chat_channels: {
|
||||||
|
public_channels: [channel],
|
||||||
|
direct_message_channels: [],
|
||||||
|
meta: { message_bus_last_ids: {} },
|
||||||
|
tracking: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
needs.hooks.beforeEach(function () {
|
||||||
|
pretender.post(`/chat/1`, () => OK());
|
||||||
|
pretender.put(`/chat/1/edit/${messageId}`, () => OK());
|
||||||
|
pretender.post(`/chat/drafts`, () => OK());
|
||||||
|
pretender.put(`/chat/api/channels/1/read/1`, () => OK());
|
||||||
|
pretender.delete(`/chat/api/channels/1/messages/${messageId}`, () => OK());
|
||||||
|
pretender.put(`/chat/api/channels/1/messages/${messageId}/restore`, () =>
|
||||||
|
OK()
|
||||||
|
);
|
||||||
|
|
||||||
|
pretender.get(`/chat/api/channels/1`, () =>
|
||||||
|
OK({
|
||||||
|
channel,
|
||||||
|
chat_messages: [message],
|
||||||
|
meta: { can_delete_self: true },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setupAutocompleteResponses([mentionedUser2, mentionedUser3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("just posted messages | it shows status on mentions ", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
await typeWithAutocompleteAndSend(`mentioning @${mentionedUser2.username}`);
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
mentionedUser2.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("just posted messages | it updates status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
await typeWithAutocompleteAndSend(`mentioning @${mentionedUser2.username}`);
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser2.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser2.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("just posted messages | it deletes status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await typeWithAutocompleteAndSend(`mentioning @${mentionedUser2.username}`);
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser2.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser2.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("edited messages | it shows status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await editMessage(
|
||||||
|
".chat-message-content",
|
||||||
|
`mentioning @${mentionedUser3.username}`
|
||||||
|
);
|
||||||
|
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser3.username),
|
||||||
|
mentionedUser3.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("edited messages | it updates status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
await editMessage(
|
||||||
|
".chat-message-content",
|
||||||
|
`mentioning @${mentionedUser3.username}`
|
||||||
|
);
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser3.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser3.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("edited messages | it deletes status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await editMessage(
|
||||||
|
".chat-message-content",
|
||||||
|
`mentioning @${mentionedUser3.username}`
|
||||||
|
);
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser3.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser3.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleted messages | it shows status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await click(".chat-message-expand");
|
||||||
|
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser1.username),
|
||||||
|
mentionedUser1.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleted messages | it updates status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await click(".chat-message-expand");
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser1.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleted messages | it deletes status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await click(".chat-message-expand");
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser1.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restored messages | it shows status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await restoreMessage(".chat-message-deleted");
|
||||||
|
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser1.username),
|
||||||
|
mentionedUser1.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restored messages | it updates status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await restoreMessage(".chat-message-deleted");
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser1.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(assert, selector, newStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restored messages | it deletes status on mentions", async function (assert) {
|
||||||
|
await visit(`/chat/c/-/${channelId}`);
|
||||||
|
|
||||||
|
await deleteMessage(".chat-message-content");
|
||||||
|
await restoreMessage(".chat-message-deleted");
|
||||||
|
|
||||||
|
loggedInUser().appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser1.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser1.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertStatusIsRendered(assert, selector, status) {
|
||||||
|
assert
|
||||||
|
.dom(selector)
|
||||||
|
.exists("status is rendered")
|
||||||
|
.hasAttribute(
|
||||||
|
"title",
|
||||||
|
status.description,
|
||||||
|
"status description is updated"
|
||||||
|
)
|
||||||
|
.hasAttribute(
|
||||||
|
"src",
|
||||||
|
new RegExp(`${status.emoji}.png`),
|
||||||
|
"status emoji is updated"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMessage(messageSelector) {
|
||||||
|
await triggerEvent(query(messageSelector), "mouseenter");
|
||||||
|
await click(".more-buttons .select-kit-header-wrapper");
|
||||||
|
await click(".select-kit-collection .select-kit-row[data-value='delete']");
|
||||||
|
await publishToMessageBus(`/chat/${channelId}`, {
|
||||||
|
type: "delete",
|
||||||
|
deleted_id: messageId,
|
||||||
|
deleted_at: "2022-01-01T08:00:00.000Z",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editMessage(messageSelector, text) {
|
||||||
|
await triggerEvent(query(messageSelector), "mouseenter");
|
||||||
|
await click(".more-buttons .select-kit-header-wrapper");
|
||||||
|
await click(".select-kit-collection .select-kit-row[data-value='edit']");
|
||||||
|
await typeWithAutocompleteAndSend(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreMessage(messageSelector) {
|
||||||
|
await triggerEvent(query(messageSelector), "mouseenter");
|
||||||
|
await click(".more-buttons .select-kit-header-wrapper");
|
||||||
|
await click(".select-kit-collection .select-kit-row[data-value='restore']");
|
||||||
|
await publishToMessageBus(`/chat/${channelId}`, {
|
||||||
|
type: "restore",
|
||||||
|
chat_message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function typeWithAutocompleteAndSend(text) {
|
||||||
|
await emulateAutocomplete(".chat-composer__input", text);
|
||||||
|
await click(".autocomplete.ac-user .selected");
|
||||||
|
await triggerKeyEvent(".chat-composer__input", "keydown", "Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupAutocompleteResponses(results) {
|
||||||
|
pretender.get("/u/search/users", () => {
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
users: results,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
pretender.get("/chat/api/mentions/groups.json", () => {
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
unreachable: [],
|
||||||
|
over_members_limit: [],
|
||||||
|
invalid: ["and"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusSelector(username) {
|
||||||
|
return `.mention[href='/u/${username}'] .user-status`;
|
||||||
|
}
|
||||||
|
});
|
200
plugins/chat/test/javascripts/components/chat-channel-test.js
Normal file
200
plugins/chat/test/javascripts/components/chat-channel-test.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import hbs from "htmlbars-inline-precompile";
|
||||||
|
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
|
import { render, waitFor } from "@ember/test-helpers";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import pretender, { OK } from "discourse/tests/helpers/create-pretender";
|
||||||
|
import { publishToMessageBus } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
|
||||||
|
module(
|
||||||
|
"Discourse Chat | Component | chat-channel | status on mentions",
|
||||||
|
function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
const channelId = 1;
|
||||||
|
const channel = {
|
||||||
|
id: channelId,
|
||||||
|
chatable_id: 1,
|
||||||
|
chatable_type: "Category",
|
||||||
|
meta: { message_bus_last_ids: {} },
|
||||||
|
current_user_membership: { following: true },
|
||||||
|
chatable: { id: 1 },
|
||||||
|
};
|
||||||
|
const actingUser = {
|
||||||
|
id: 1,
|
||||||
|
username: "acting_user",
|
||||||
|
};
|
||||||
|
const mentionedUser = {
|
||||||
|
id: 1000,
|
||||||
|
username: "user1",
|
||||||
|
status: {
|
||||||
|
description: "surfing",
|
||||||
|
emoji: "surfing_man",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mentionedUser2 = {
|
||||||
|
id: 2000,
|
||||||
|
username: "user2",
|
||||||
|
status: {
|
||||||
|
description: "vacation",
|
||||||
|
emoji: "desert_island",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const message = {
|
||||||
|
id: 1891,
|
||||||
|
message: `Hey @${mentionedUser.username}`,
|
||||||
|
cooked: `<p>Hey <a class="mention" href="/u/${mentionedUser.username}">@${mentionedUser.username}</a></p>`,
|
||||||
|
mentioned_users: [mentionedUser],
|
||||||
|
user: {
|
||||||
|
id: 1,
|
||||||
|
username: "jesse",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
pretender.get(`/chat/api/channels/1`, () =>
|
||||||
|
OK({
|
||||||
|
channel,
|
||||||
|
chat_messages: [message],
|
||||||
|
meta: { can_delete_self: true },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.channel = fabricators.channel({
|
||||||
|
id: channelId,
|
||||||
|
currentUserMembership: { following: true },
|
||||||
|
meta: { can_join_chat_channel: false },
|
||||||
|
});
|
||||||
|
this.appEvents = this.container.lookup("service:appEvents");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it shows status on mentions", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser.username),
|
||||||
|
mentionedUser.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it updates status on mentions", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
|
||||||
|
const newStatus = {
|
||||||
|
description: "off to dentist",
|
||||||
|
emoji: "tooth",
|
||||||
|
};
|
||||||
|
|
||||||
|
this.appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser.username),
|
||||||
|
newStatus
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it deletes status on mentions", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
|
||||||
|
this.appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it shows status on mentions on messages that came from Message Bus", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
|
||||||
|
await receiveChatMessageViaMessageBus();
|
||||||
|
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
mentionedUser2.status
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it updates status on mentions on messages that came from Message Bus", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
await receiveChatMessageViaMessageBus();
|
||||||
|
|
||||||
|
const newStatus = {
|
||||||
|
description: "off to meeting",
|
||||||
|
emoji: "calendar",
|
||||||
|
};
|
||||||
|
this.appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser2.id]: newStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser2.username);
|
||||||
|
await waitFor(selector);
|
||||||
|
assertStatusIsRendered(
|
||||||
|
assert,
|
||||||
|
statusSelector(mentionedUser2.username),
|
||||||
|
newStatus
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it deletes status on mentions on messages that came from Message Bus", async function (assert) {
|
||||||
|
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
|
||||||
|
await receiveChatMessageViaMessageBus();
|
||||||
|
|
||||||
|
this.appEvents.trigger("user-status:changed", {
|
||||||
|
[mentionedUser2.id]: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selector = statusSelector(mentionedUser2.username);
|
||||||
|
await waitFor(selector, { count: 0 });
|
||||||
|
assert.dom(selector).doesNotExist("status is deleted");
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertStatusIsRendered(assert, selector, status) {
|
||||||
|
assert
|
||||||
|
.dom(selector)
|
||||||
|
.exists("status is rendered")
|
||||||
|
.hasAttribute(
|
||||||
|
"title",
|
||||||
|
status.description,
|
||||||
|
"status description is updated"
|
||||||
|
)
|
||||||
|
.hasAttribute(
|
||||||
|
"src",
|
||||||
|
new RegExp(`${status.emoji}.png`),
|
||||||
|
"status emoji is updated"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function receiveChatMessageViaMessageBus() {
|
||||||
|
await publishToMessageBus(`/chat/${channelId}`, {
|
||||||
|
chat_message: {
|
||||||
|
id: 2138,
|
||||||
|
message: `Hey @${mentionedUser2.username}`,
|
||||||
|
cooked: `<p>Hey <a class="mention" href="/u/${mentionedUser2.username}">@${mentionedUser2.username}</a></p>`,
|
||||||
|
created_at: "2023-05-18T16:07:59.588Z",
|
||||||
|
excerpt: `Hey @${mentionedUser2.username}`,
|
||||||
|
available_flags: [],
|
||||||
|
thread_title: null,
|
||||||
|
chat_channel_id: 7,
|
||||||
|
mentioned_users: [mentionedUser2],
|
||||||
|
user: actingUser,
|
||||||
|
chat_webhook_event: null,
|
||||||
|
uploads: [],
|
||||||
|
},
|
||||||
|
type: "sent",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusSelector(username) {
|
||||||
|
return `.mention[href='/u/${username}'] .user-status`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
Reference in New Issue
Block a user