FIX: correctly handle subscriptions (#24270)

Subscriptions manager have been a pain since the beginning, one of the problem is that thread and channels behave mostly the same but with various small difference which I expect to increase over time.

Trying to use subclasses for this case has proven to be a mistake, this commit now uses a class for each case (channel, thread) which for now contains a lot of duplication, which might be reduced in the future but has the merit to make reasoning about each case very simple.

This refactor is fixing a bug introduced in 90efdd7f9d which was causing the wrong channel to be unsubscribed, this shouldn't be possible anymore. We had tests for this which were disabled due to flakeyness, I will consider re-enabling them in the future.

Other notes:
- notices had been added to the subscriptions manager service, they have been moved into their own dedicated service: `ChatChannelNoticesManager`
- the `(each model)` trick used in `<ChatChannel />` since 90efdd7f9d to ensure atomicity has been applied to `<ChatThread />` too
This commit is contained in:
Joffrey JAFFEUX 2023-11-07 16:37:42 +01:00 committed by GitHub
parent 1d68ff430b
commit dcaa719363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 358 additions and 265 deletions

View File

@ -13,6 +13,7 @@ import {
} from "discourse/lib/user-presence";
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import ChatChannelSubscriptionManager from "discourse/plugins/chat/discourse/lib/chat-channel-subscription-manager";
import {
FUTURE,
PAST,
@ -29,9 +30,6 @@ import {
scrollListToMessage,
} from "discourse/plugins/chat/discourse/lib/scroll-helpers";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
// TODO (martin) Remove this when the handleSentMessage logic inside chatChannelPaneSubscriptionsManager
// is moved over from this file completely.
import { handleStagedMessage } from "discourse/plugins/chat/discourse/services/chat-pane-base-subscriptions-manager";
import { stackingContextFix } from "../lib/chat-ios-hacks";
export default class ChatChannel extends Component {
@ -40,7 +38,6 @@ export default class ChatChannel extends Component {
@service chat;
@service chatApi;
@service chatChannelsManager;
@service chatChannelPaneSubscriptionsManager;
@service chatComposerPresenceManager;
@service chatDraftsManager;
@service chatEmojiPickerManager;
@ -97,7 +94,7 @@ export default class ChatChannel extends Component {
teardownListeners() {
this.#cancelHandlers();
removeOnPresenceChange(this.onPresenceChangeCallback);
this.unsubscribeToUpdates(this.args.channel.id);
this.subscriptionManager.teardown();
}
@action
@ -140,7 +137,11 @@ export default class ChatChannel extends Component {
return;
}
this.subscribeToUpdates(this.args.channel);
this.subscriptionManager = new ChatChannelSubscriptionManager(
this,
this.args.channel,
{ onNewMessage: this.onNewMessage }
);
if (this.args.targetMessageId) {
this.debounceHighlightOrFetchMessage(this.args.targetMessageId);
@ -149,6 +150,14 @@ export default class ChatChannel extends Component {
}
}
@bind
onNewMessage(message) {
stackingContextFix(this.scrollable, () => {
this.messagesManager.addMessages([message]);
});
this.debouncedUpdateLastReadMessage();
}
@bind
onPresenceChangeCallback(present) {
if (present) {
@ -464,36 +473,6 @@ export default class ChatChannel extends Component {
}
}
@bind
onMessage(data) {
switch (data.type) {
case "sent":
this.handleSentMessage(data);
break;
}
}
handleSentMessage(data) {
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
const stagedMessage = handleStagedMessage(
this.args.channel,
this.messagesManager,
data
);
if (stagedMessage) {
return;
}
}
const message = ChatMessage.create(this.args.channel, data.chat_message);
message.manager = this.args.channel.messagesManager;
stackingContextFix(this.scrollable, () => {
this.messagesManager.addMessages([message]);
});
this.debouncedUpdateLastReadMessage();
this.args.channel.lastMessage = message;
}
@action
async onSendMessage(message) {
await message.cook();
@ -620,28 +599,6 @@ export default class ChatChannel extends Component {
});
}
unsubscribeToUpdates(channelId) {
if (!channelId) {
return;
}
this.chatChannelPaneSubscriptionsManager.unsubscribe();
this.messageBus.unsubscribe(`/chat/${channelId}`, this.onMessage);
}
subscribeToUpdates(channel) {
if (!channel) {
return;
}
this.messageBus.subscribe(
`/chat/${channel.id}`,
this.onMessage,
channel.channelMessageBusLastId
);
this.chatChannelPaneSubscriptionsManager.subscribe(channel);
}
@action
addAutoFocusEventListener() {
document.addEventListener("keydown", this._autoFocus);

View File

@ -17,10 +17,12 @@
>
{{#if this.chat.activeChannel}}
{{#each (array this.chat.activeChannel) as |channel|}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{#if channel}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{/if}}
{{/each}}
{{/if}}
</div>

View File

@ -1,4 +1,5 @@
import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
@ -88,12 +89,14 @@ export default class ChatDrawerThread extends Component {
{{didUpdate this.fetchChannelAndThread @params.channelId}}
{{didUpdate this.fetchChannelAndThread @params.threadId}}
>
{{#if this.chat.activeChannel.activeThread}}
<ChatThread
@thread={{this.chat.activeChannel.activeThread}}
@targetMessageId={{@params.messageId}}
/>
{{/if}}
{{#each (array this.chat.activeChannel.activeThread) as |thread|}}
{{#if thread}}
<ChatThread
@thread={{thread}}
@targetMessageId={{@params.messageId}}
/>
{{/if}}
{{/each}}
</div>
{{/if}}
</template>

View File

@ -8,11 +8,11 @@ const COMPONENT_DICT = {
};
export default class ChatNotices extends Component {
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
@service("chat-channel-notices-manager") noticesManager;
@action
clearNotice() {
this.subscriptionsManager.clearNotice(this.args.notice);
this.noticesManager.clearNotice(this.args.notice);
}
get component() {

View File

@ -2,10 +2,10 @@ import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
export default class ChatNotices extends Component {
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
@service("chat-channel-notices-manager") noticesManager;
get noticesForChannel() {
return this.subscriptionsManager.notices.filter(
return this.noticesManager.notices.filter(
(notice) => notice.channelId === this.args.channel.id
);
}

View File

@ -6,7 +6,6 @@
data-id={{@thread.id}}
{{did-insert this.setUploadDropZone}}
{{did-insert this.didUpdateThread}}
{{did-update this.didUpdateThread @thread.id}}
{{will-destroy this.teardown}}
>
{{#if @includeHeader}}

View File

@ -9,6 +9,7 @@ import { resetIdle } from "discourse/lib/desktop-notifications";
import { NotificationLevels } from "discourse/lib/notification-levels";
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import ChatChannelThreadSubscriptionManager from "discourse/plugins/chat/discourse/lib/chat-channel-thread-subscription-manager";
import {
FUTURE,
PAST,
@ -36,7 +37,6 @@ export default class ChatThread extends Component {
@service chatHistory;
@service chatThreadComposer;
@service chatThreadPane;
@service chatThreadPaneSubscriptionsManager;
@service currentUser;
@service router;
@service siteSettings;
@ -85,14 +85,9 @@ export default class ChatThread extends Component {
this.uploadDropZone = element;
}
@action
subscribeToUpdates() {
this.chatThreadPaneSubscriptionsManager.subscribe(this.args.thread);
}
@action
teardown() {
this.chatThreadPaneSubscriptionsManager.unsubscribe();
this.subscriptionManager.teardown();
cancel(this._debouncedFillPaneAttemptHandler);
cancel(this._debounceUpdateLastReadMessageHandler);
}
@ -166,7 +161,11 @@ export default class ChatThread extends Component {
@action
loadMessages() {
this.fetchMessages();
this.subscribeToUpdates();
this.subscriptionManager = new ChatChannelThreadSubscriptionManager(
this,
this.args.thread,
{ onNewMessage: this.onNewMessage }
);
}
@action
@ -296,6 +295,11 @@ export default class ChatThread extends Component {
scrollListToMessage(this.scrollable, message, opts);
}
@bind
onNewMessage(message) {
this.messagesManager.addMessages([message]);
}
@bind
processMessages(thread, result) {
const messages = result.messages.map((messageData) => {

View File

@ -1,82 +1,46 @@
import Service, { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import { getOwner, setOwner } from "@ember/application";
import { inject as service } from "@ember/service";
import { cloneJSON } from "discourse-common/lib/object";
import { bind } from "discourse-common/utils/decorators";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import ChatThreadPreview from "discourse/plugins/chat/discourse/models/chat-thread-preview";
export function handleStagedMessage(channel, messagesManager, data) {
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
if (!stagedMessage) {
return;
}
stagedMessage.error = null;
stagedMessage.id = data.chat_message.id;
stagedMessage.staged = false;
stagedMessage.excerpt = data.chat_message.excerpt;
stagedMessage.channel = channel;
stagedMessage.createdAt = new Date(data.chat_message.created_at);
return stagedMessage;
}
/**
* Handles subscriptions for MessageBus messages sent from Chat::Publisher
* to the channel and thread panes. There are individual services for
* each (ChatChannelPaneSubscriptionsManager and ChatThreadPaneSubscriptionsManager)
* that implement their own logic where necessary. Functions which will
* always be different between the two raise a "not implemented" error in
* the base class, and the child class must define the associated function,
* even if it is a noop in that context.
*
* For example, in the thread context there is no need to handle the thread
* creation event, because the panel will not be open in that case.
*/
export default class ChatPaneBaseSubscriptionsManager extends Service {
@service chat;
export default class ChatChannelSubscriptionManager {
@service currentUser;
@service chatChannelNoticesManager;
@service messageBus;
messageBusChannel = null;
messageBusLastId = null;
@tracked channel;
get messagesManager() {
return this.model.messagesManager;
}
constructor(context, channel, { onNewMessage } = {}) {
setOwner(this, getOwner(context));
beforeSubscribe() {}
afterMessage() {}
subscribe(model) {
this.unsubscribe();
this.beforeSubscribe(model);
this.model = model;
if (!this.messageBusChannel) {
return;
}
this.channel = channel;
this.onNewMessage = onNewMessage;
this.messageBus.subscribe(
this.messageBusChannel,
this.onMessage,
this.messageBusLastId
this.channel.channelMessageBusLastId
);
}
unsubscribe() {
if (!this.model) {
return;
}
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
this.model = null;
get messagesManager() {
return this.channel.messagesManager;
}
handleStagedMessageInternal(channel, data) {
return handleStagedMessage(channel, this.messagesManager, data);
get messageBusChannel() {
return `/chat/${this.channel.id}`;
}
teardown() {
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
this.modelId = null;
}
@bind
onMessage(busData) {
onMessage(busData, _, __, lastMessageBusId) {
switch (busData.type) {
case "sent":
this.handleSentMessage(busData);
@ -119,11 +83,42 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
break;
}
this.afterMessage(this.model, ...arguments);
this.channel.channelMessageBusLastId = lastMessageBusId;
}
handleSentMessage() {
throw "not implemented";
handleSentMessage(data) {
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
const stagedMessage = this.handleStagedMessage(
this.channel,
this.messagesManager,
data
);
if (stagedMessage) {
return;
}
}
const message = ChatMessage.create(this.channel, data.chat_message);
message.manager = this.channel.messagesManager;
this.onNewMessage?.(message);
this.channel.lastMessage = message;
}
handleStagedMessage(channel, messagesManager, data) {
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
if (!stagedMessage) {
return;
}
stagedMessage.error = null;
stagedMessage.id = data.chat_message.id;
stagedMessage.staged = false;
stagedMessage.excerpt = data.chat_message.excerpt;
stagedMessage.channel = channel;
stagedMessage.createdAt = new Date(data.chat_message.created_at);
return stagedMessage;
}
handleProcessedMessage(data) {
@ -182,7 +177,10 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
this.messagesManager.removeMessage(targetMsg);
}
this._afterDeleteMessage(targetMsg, data);
if (this.channel.currentUserMembership.lastReadMessageId === targetMsg.id) {
this.channel.currentUserMembership.lastReadMessageId =
data.latest_not_deleted_message_id;
}
}
handleRestoreMessage(data) {
@ -211,10 +209,10 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
}
handleNewThreadCreated(data) {
this.model.threadsManager
.find(this.model.id, data.thread_id, { fetchIfNotFound: true })
this.channel.threadsManager
.find(this.channel.id, data.thread_id, { fetchIfNotFound: true })
.then((thread) => {
const channelOriginalMessage = this.model.messagesManager.findMessage(
const channelOriginalMessage = this.channel.messagesManager.findMessage(
thread.originalMessage.id
);
@ -224,15 +222,14 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
});
}
handleThreadOriginalMessageUpdate() {
throw "not implemented";
handleNotice(data) {
this.chatChannelNoticesManager.handleNotice(data);
}
handleNotice() {
throw "not implemented";
}
_afterDeleteMessage() {
throw "not implemented";
handleThreadOriginalMessageUpdate(data) {
const message = this.messagesManager.findMessage(data.original_message_id);
if (message?.thread) {
message.thread.preview = ChatThreadPreview.create(data.preview);
}
}
}

View File

@ -0,0 +1,215 @@
import { tracked } from "@glimmer/tracking";
import { getOwner, setOwner } from "@ember/application";
import { inject as service } from "@ember/service";
import { cloneJSON } from "discourse-common/lib/object";
import { bind } from "discourse-common/utils/decorators";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
export default class ChatChannelThreadSubscriptionManager {
@service currentUser;
@service messageBus;
@tracked channel;
constructor(context, thread, { onNewMessage } = {}) {
setOwner(this, getOwner(context));
this.thread = thread;
this.onNewMessage = onNewMessage;
this.messageBus.subscribe(
this.messageBusChannel,
this.onMessage,
this.thread.channelMessageBusLastId
);
}
get messagesManager() {
return this.thread.messagesManager;
}
get messageBusChannel() {
return `/chat/${this.thread.channel.id}/thread/${this.thread.id}`;
}
teardown() {
this.messageBus.unsubscribe(this.messageBusChannel, this.onMessage);
}
@bind
onMessage(busData, _, __, lastMessageBusId) {
switch (busData.type) {
case "sent":
this.handleSentMessage(busData);
break;
case "reaction":
this.handleReactionMessage(busData);
break;
case "processed":
this.handleProcessedMessage(busData);
break;
case "edit":
this.handleEditMessage(busData);
break;
case "refresh":
this.handleRefreshMessage(busData);
break;
case "delete":
this.handleDeleteMessage(busData);
break;
case "bulk_delete":
this.handleBulkDeleteMessage(busData);
break;
case "restore":
this.handleRestoreMessage(busData);
break;
case "self_flagged":
this.handleSelfFlaggedMessage(busData);
break;
case "flag":
this.handleFlaggedMessage(busData);
break;
case "thread_created":
this.handleNewThreadCreated(busData);
break;
}
this.thread.threadMessageBusLastId = lastMessageBusId;
}
handleSentMessage(data) {
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
const stagedMessage = this.handleStagedMessage(
this.thread.channel,
this.messagesManager,
data
);
if (stagedMessage) {
return;
}
}
const message = ChatMessage.create(this.thread.channel, data.chat_message);
message.thread = this.thread;
message.manager = this.messagesManager;
this.onNewMessage?.(message);
}
handleStagedMessage(channel, messagesManager, data) {
const stagedMessage = messagesManager.findStagedMessage(data.staged_id);
if (!stagedMessage) {
return;
}
stagedMessage.error = null;
stagedMessage.id = data.chat_message.id;
stagedMessage.staged = false;
stagedMessage.excerpt = data.chat_message.excerpt;
stagedMessage.channel = channel;
stagedMessage.createdAt = new Date(data.chat_message.created_at);
return stagedMessage;
}
handleProcessedMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message.id);
if (message) {
message.cooked = data.chat_message.cooked;
message.processed = true;
}
}
handleReactionMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message_id);
if (message) {
message.react(data.emoji, data.action, data.user, this.currentUser.id);
}
}
handleEditMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message.id);
if (message) {
message.excerpt = data.chat_message.excerpt;
message.uploads = cloneJSON(data.chat_message.uploads || []);
message.edited = data.chat_message.edited;
}
}
handleRefreshMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message.id);
if (message) {
message.incrementVersion();
}
}
handleBulkDeleteMessage(data) {
data.deleted_ids.forEach((deletedId) => {
this.handleDeleteMessage({
deleted_id: deletedId,
deleted_at: data.deleted_at,
});
});
}
handleDeleteMessage(data) {
const deletedId = data.deleted_id;
const targetMsg = this.messagesManager.findMessage(deletedId);
if (!targetMsg) {
return;
}
if (this.currentUser.staff || this.currentUser.id === targetMsg.user.id) {
targetMsg.deletedAt = data.deleted_at;
targetMsg.deletedById = data.deleted_by_id;
targetMsg.expanded = false;
} else {
this.messagesManager.removeMessage(targetMsg);
}
if (this.thread.currentUserMembership?.lastReadMessageId === targetMsg.id) {
this.thread.currentUserMembership.lastReadMessageId =
data.latest_not_deleted_message_id;
}
}
handleRestoreMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message.id);
if (message) {
message.deletedAt = null;
} else {
const newMessage = ChatMessage.create(this.model, data.chat_message);
newMessage.manager = this.messagesManager;
this.messagesManager.addMessages([newMessage]);
}
}
handleSelfFlaggedMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message_id);
if (message) {
message.userFlagStatus = data.user_flag_status;
}
}
handleFlaggedMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message_id);
if (message) {
message.reviewableId = data.reviewable_id;
}
}
handleNewThreadCreated(data) {
this.thread.threadsManager
.find(this.thread.id, data.thread_id, { fetchIfNotFound: true })
.then((thread) => {
const channelOriginalMessage = this.thread.messagesManager.findMessage(
thread.originalMessage.id
);
if (channelOriginalMessage) {
channelOriginalMessage.thread = thread;
}
});
}
}

View File

@ -0,0 +1,16 @@
import { tracked } from "@glimmer/tracking";
import Service from "@ember/service";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import ChatNotice from "../models/chat-notice";
export default class ChatChannelNoticesManager extends Service {
@tracked notices = new TrackedArray();
handleNotice(data) {
this.notices.pushObject(ChatNotice.create(data));
}
clearNotice(notice) {
this.notices.removeObject(notice);
}
}

View File

@ -1,48 +0,0 @@
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import ChatNotice from "../models/chat-notice";
import ChatThreadPreview from "../models/chat-thread-preview";
import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-manager";
export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
@service chat;
@service currentUser;
@tracked notices = new TrackedArray();
beforeSubscribe(model) {
this.messageBusChannel = `/chat/${model.id}`;
this.messageBusLastId = model.channelMessageBusLastId;
}
afterMessage(model, _, __, lastMessageBusId) {
model.channelMessageBusLastId = lastMessageBusId;
}
handleSentMessage() {
return;
}
handleNotice(data) {
this.notices.pushObject(ChatNotice.create(data));
}
clearNotice(notice) {
this.notices.removeObject(notice);
}
handleThreadOriginalMessageUpdate(data) {
const message = this.messagesManager.findMessage(data.original_message_id);
if (message?.thread) {
message.thread.preview = ChatThreadPreview.create(data.preview);
}
}
_afterDeleteMessage(targetMsg, data) {
if (this.model.currentUserMembership.lastReadMessageId === targetMsg.id) {
this.model.currentUserMembership.lastReadMessageId =
data.latest_not_deleted_message_id;
}
}
}

View File

@ -1,48 +0,0 @@
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import ChatPaneBaseSubscriptionsManager from "./chat-pane-base-subscriptions-manager";
export default class ChatThreadPaneSubscriptionsManager extends ChatPaneBaseSubscriptionsManager {
beforeSubscribe(model) {
this.messageBusChannel = `/chat/${model.channel.id}/thread/${model.id}`;
this.messageBusLastId = model.threadMessageBusLastId;
}
afterMessage(model, _, __, lastMessageBusId) {
model.threadMessageBusLastId = lastMessageBusId;
}
handleSentMessage(data) {
if (data.chat_message.user.id === this.currentUser.id && data.staged_id) {
const stagedMessage = this.handleStagedMessageInternal(
this.model.channel,
data
);
if (stagedMessage) {
return;
}
}
const message = ChatMessage.create(this.model.channel, data.chat_message);
message.thread = this.model;
message.manager = this.messagesManager;
this.messagesManager.addMessages([message]);
}
// NOTE: noop, there is nothing to do when a thread original message
// is updated inside the thread panel (for now).
handleThreadOriginalMessageUpdate() {
return;
}
// NOTE: We don't yet handle notices inside of threads so do nothing.
handleNotice() {
return;
}
_afterDeleteMessage(targetMsg, data) {
if (this.model.currentUserMembership?.lastReadMessageId === targetMsg.id) {
this.model.currentUserMembership.lastReadMessageId =
data.latest_not_deleted_message_id;
}
}
}

View File

@ -1,5 +1,7 @@
<ChatThread
@thread={{this.model}}
@targetMessageId={{this.targetMessageId}}
@includeHeader={{true}}
/>
{{#each (array this.model) as |thread|}}
<ChatThread
@thread={{thread}}
@targetMessageId={{this.targetMessageId}}
@includeHeader={{true}}
/>
{{/each}}

View File

@ -12,9 +12,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
test("displays all notices for a channel", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
this.manager = this.container.lookup("service:chatChannelNoticesManager");
this.manager.handleNotice({
channel_id: this.channel.id,
text_content: "hello",
@ -40,9 +38,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
test("Notices can be cleared", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
this.manager = this.container.lookup("service:chatChannelNoticesManager");
this.manager.handleNotice({
channel_id: this.channel.id,
text_content: "hello",
@ -66,9 +62,7 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
});
test("MentionWithoutMembership notice renders", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
this.manager = this.container.lookup("service:chatChannelNoticesManager");
const text = "Joffrey can't chat, hermano";
this.manager.handleNotice({
channel_id: this.channel.id,