From 60c67afba4eb5e53367cb3f79e2fbabc526a7674 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 17 May 2023 17:49:52 +0200 Subject: [PATCH] DEV: various improvements to devex on chat (#21612) - Improves styleguide support - Adds toggle color scheme to styleguide - Adds properties mutators to styleguide - Attempts to quit a session as soon as done with it in system specs, this should at least free resources faster - Refactors fabricators to simplify them - Adds more fabricators (uploads for example) - Starts implementing components pattern in system specs - Uses Chat::Message creator to create messages in system specs, this should help to have more real specs as the side effects should now happen --- .../discourse/components/chat-channel.hbs | 1 - .../discourse/components/chat-channel.js | 9 +- .../chat-composer-message-details.hbs | 4 +- .../discourse/components/chat-composer.hbs | 5 +- .../discourse/components/chat-composer.js | 5 +- .../chat-message-actions-desktop.js | 4 + .../discourse/components/chat-message.js | 9 +- .../discourse/components/chat-thread.hbs | 1 - .../components/chat/thread/list-item.hbs | 5 +- .../chat-composer-message-details.hbs | 15 ++ .../chat-composer-message-details.js | 27 +++ .../components/styleguide/chat-composer.hbs | 23 +++ .../components/styleguide/chat-composer.js | 30 ++++ .../components/styleguide/chat-message.hbs | 54 ++++++ .../components/styleguide/chat-message.js | 86 +++++++++ .../chat-thread-original-message.hbs | 5 + .../chat-thread-original-message.js | 6 + .../javascripts/discourse/lib/fabricators.js | 170 ++++++++++++++++++ .../discourse/models/chat-channel.js | 6 +- .../discourse/models/chat-direct-message.js | 7 +- .../discourse/models/chat-message-reaction.js | 2 +- .../discourse/models/chat-message.js | 69 ++++--- .../discourse/models/chat-thread.js | 4 + .../services/chat-channel-composer.js | 1 - .../templates/styleguide/organisms/chat.hbs | 4 + plugins/chat/config/locales/client.en.yml | 5 + .../chat/spec/fabricators/chat_fabricator.rb | 30 +++- .../chat/composer/shortcuts/thread_spec.rb | 51 ++++++ plugins/chat/spec/system/chat_channel_spec.rb | 10 +- .../chat/spec/system/edited_message_spec.rb | 3 +- .../message_notifications_mobile_spec.rb | 45 ++++- ...message_notifications_with_sidebar_spec.rb | 48 +++-- .../spec/system/page_objects/chat/chat.rb | 4 +- .../system/page_objects/chat/chat_thread.rb | 9 + .../page_objects/chat/components/composer.rb | 33 ++++ .../components/composer_message_details.rb | 25 +++ .../system/reply_to_message/drawer_spec.rb | 2 +- .../system/reply_to_message/full_page_spec.rb | 2 +- .../system/reply_to_message/mobile_spec.rb | 2 +- .../chat/spec/system/single_thread_spec.rb | 6 +- .../spec/system/unfollow_dm_channel_spec.rb | 3 +- .../user_menu_notifications/sidebar_spec.rb | 3 +- .../chat-channel-archive-modal-inner-test.js | 4 +- .../components/chat-channel-card-test.js | 4 +- .../chat-channel-delete-modal-inner-test.js | 4 +- .../components/chat-channel-leave-btn-test.js | 10 +- .../components/chat-channel-metadata-test.js | 6 +- .../chat-channel-preview-card-test.js | 7 +- .../components/chat-channel-row-test.js | 24 ++- .../components/chat-channel-status-test.js | 12 +- .../components/chat-channel-title-test.js | 18 +- .../chat-composer-message-details-test.js | 77 ++++++++ .../components/chat-message-avatar-test.js | 6 +- .../components/chat-message-info-test.js | 22 +-- ...essage-move-to-channel-modal-inner-test.js | 4 +- .../components/chat-message-test.js | 1 - .../chat-replying-indicator-test.js | 10 +- .../chat-retention-reminder-text-test.js | 8 +- .../components/direct-message-creator-test.js | 6 +- .../test/javascripts/helpers/fabricator.js | 21 --- .../test/javascripts/helpers/fabricators.js | 60 ------- .../unit/helpers/format-chat-date-test.js | 15 +- .../unit/services/chat-guardian-test.js | 4 +- .../components/styleguide/component.hbs | 3 + .../components/styleguide/component.js | 3 + .../components/styleguide/controls.hbs | 5 + .../components/styleguide/controls.js | 3 + .../components/styleguide/controls/row.hbs | 6 + .../components/styleguide/controls/toggle.hbs | 1 + .../components/styleguide/controls/toggle.js | 3 + .../components/toggle-color-mode.hbs | 1 + .../discourse/components/toggle-color-mode.js | 45 +++++ .../discourse/templates/styleguide.hbs | 1 + .../assets/stylesheets/styleguide.scss | 30 ++++ spec/support/system_helpers.rb | 5 +- 75 files changed, 1002 insertions(+), 260 deletions(-) create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.hbs create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.hbs create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.hbs create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.js create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.hbs create mode 100644 plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.js create mode 100644 plugins/chat/assets/javascripts/discourse/lib/fabricators.js create mode 100644 plugins/chat/assets/javascripts/discourse/templates/styleguide/organisms/chat.hbs create mode 100644 plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb create mode 100644 plugins/chat/spec/system/page_objects/chat/components/composer.rb create mode 100644 plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb create mode 100644 plugins/chat/test/javascripts/components/chat-composer-message-details-test.js delete mode 100644 plugins/chat/test/javascripts/helpers/fabricator.js delete mode 100644 plugins/chat/test/javascripts/helpers/fabricators.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/row.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.js create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.hbs create mode 100644 plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.js diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel.hbs index 792bbe324dc..3eb5a4e6cf3 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel.hbs @@ -41,7 +41,6 @@ {{#each @channel.messages key="id" as |message|}} +
- {{d-icon @icon}} + {{d-icon (if @message.editing "pencil-alt" "reply")}} {{@message.user.username}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs index 57ec49e8b8e..83aa78afc25 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.hbs @@ -9,7 +9,6 @@ this.currentMessage this.currentMessage.inReplyTo }} - @icon={{if this.currentMessage.editing "pencil-alt" "reply"}} @cancelAction={{this.onCancel}} /> {{/if}} @@ -26,7 +25,7 @@ }} {{did-update this.didUpdateMessage this.currentMessage}} {{did-update this.didUpdateInReplyTo this.currentMessage.inReplyTo}} - {{did-insert this.setupAppEvents}} + {{did-insert this.setup}} {{will-destroy this.teardown}} {{will-destroy this.cancelPersistDraft}} > @@ -71,7 +70,7 @@ {{on "click" this.onSend}} @icon="paper-plane" class="chat-composer__send-btn" - title="chat.composer.send" + title={{i18n "chat.composer.send"}} disabled={{or this.disabled (not this.sendEnabled)}} tabindex={{if this.sendEnabled 0 -1}} {{on "focus" (fn this.computeIsFocused true)}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index 1bbc4f17a32..9fbbca65560 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -141,7 +141,10 @@ export default class ChatComposer extends Component { } @action - setupAppEvents() { + setup() { + this.composer.message = ChatMessage.createDraftMessage(this.args.channel, { + user: this.currentUser, + }); this.appEvents.on("chat:modify-selection", this, "modifySelection"); this.appEvents.on( "chat:open-insert-link-modal", diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.js b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.js index 788935d48ab..6bb2991f98b 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-actions-desktop.js @@ -54,6 +54,10 @@ export default class ChatMessageActionsDesktop extends Component { this.context ); + if (!messageContainer) { + return; + } + const viewport = messageContainer.closest(".popper-viewport"); this.size = viewport.clientWidth < REDUCED_WIDTH_THRESHOLD ? REDUCED : FULL; diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.js b/plugins/chat/assets/javascripts/discourse/components/chat-message.js index 5a3dbfe52b1..64e214ed4c6 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.js @@ -130,7 +130,7 @@ export default class ChatMessage extends Component { } _chatMessageDecorators.forEach((decorator) => { - decorator.call(this, this.messageContainer, this.args.channel); + decorator.call(this, this.messageContainer, this.args.message.channel); }); }); } @@ -147,7 +147,7 @@ export default class ChatMessage extends Component { !this.args.message?.deletedAt || this.currentUser.id === this.args.message?.user?.id || this.currentUser.staff || - this.args.channel?.canModerate + this.args.message?.channel?.canModerate ); } @@ -316,7 +316,10 @@ export default class ChatMessage extends Component { } get threadingEnabled() { - return this.args.channel?.threadingEnabled && !!this.args.message?.thread; + return ( + this.args.message?.channel?.threadingEnabled && + !!this.args.message?.thread + ); } get showThreadIndicator() { diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs index c2789dc3dd0..0c9143b45f5 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-thread.hbs @@ -34,7 +34,6 @@ {{#each this.thread.messages key="id" as |message|}}
- + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.hbs b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.hbs new file mode 100644 index 00000000000..b4d02f4342c --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.hbs @@ -0,0 +1,15 @@ + + + + + + + + {{#if this.message.editing}} + + {{else}} + + {{/if}} + + + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.js b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.js new file mode 100644 index 00000000000..68a60e77724 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer-message-details.js @@ -0,0 +1,27 @@ +import Component from "@glimmer/component"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; +import { action } from "@ember/object"; +import { cached } from "@glimmer/tracking"; +import { inject as service } from "@ember/service"; + +export default class ChatStyleguideChatComposerMessageDetails extends Component { + @service site; + @service session; + @service keyValueStore; + + @cached + get message() { + return fabricators.message(); + } + + @action + toggleMode() { + if (this.message.editing) { + this.message.editing = false; + this.message.inReplyTo = fabricators.message(); + } else { + this.message.editing = true; + this.message.inReplyTo = null; + } + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.hbs b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.hbs new file mode 100644 index 00000000000..24f529be21d --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.hbs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.js new file mode 100644 index 00000000000..feb5d069630 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-composer.js @@ -0,0 +1,30 @@ +import Component from "@glimmer/component"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel"; + +export default class ChatStyleguideChatComposer extends Component { + @service chatChannelComposer; + @service chatChannelPane; + + channel = fabricators.channel(); + + @action + toggleDisabled() { + if (this.channel.status === CHANNEL_STATUSES.open) { + this.channel.status = CHANNEL_STATUSES.readOnly; + } else { + this.channel.status = CHANNEL_STATUSES.open; + } + } + @action + toggleSending() { + this.chatChannelPane.sending = !this.chatChannelPane.sending; + } + + @action + onSendMessage() { + this.chatChannelComposer.reset(); + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.hbs b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.hbs new file mode 100644 index 00000000000..cfeffcc007a --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.hbs @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.js b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.js new file mode 100644 index 00000000000..5088d5c4437 --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-message.js @@ -0,0 +1,86 @@ +import Component from "@glimmer/component"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; +import { action } from "@ember/object"; +import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messages-manager"; +import { getOwner } from "discourse-common/lib/get-owner"; + +export default class ChatStyleguideChatMessage extends Component { + manager = new ChatMessagesManager(getOwner(this)); + + message = fabricators.message(); + + @action + toggleDeleted() { + if (this.message.deletedAt) { + this.message.deletedAt = null; + } else { + this.message.deletedAt = moment(); + } + } + + @action + toggleBookmarked() { + if (this.message.bookmark) { + this.message.bookmark = null; + } else { + this.message.bookmark = fabricators.bookmark(); + } + } + + @action + toggleHighlighted() { + this.message.highlighted = !this.message.highlighted; + } + + @action + toggleEdited() { + this.message.edited = !this.message.edited; + } + + @action + toggleLastVisit() { + this.message.newest = !this.message.newest; + } + + @action + toggleThread() { + if (this.message.thread) { + this.message.channel.threadingEnabled = false; + this.message.thread = null; + this.message.threadReplyCount = 0; + } else { + this.message.thread = fabricators.thread({ + channel: this.message.channel, + }); + this.message.threadReplyCount = 1; + this.message.channel.threadingEnabled = true; + } + } + + @action + updateMessage(event) { + this.message.message = event.target.value; + this.message.cook(); + } + + @action + toggleReaction() { + if (this.message.reactions?.length) { + this.message.reactions = []; + } else { + this.message.reactions = [ + fabricators.reaction({ emoji: "heart" }), + fabricators.reaction({ emoji: "rocket", reacted: true }), + ]; + } + } + + @action + toggleUpload() { + if (this.message.uploads?.length) { + this.message.uploads = []; + } else { + this.message.uploads = [fabricators.upload(), fabricators.upload()]; + } + } +} diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.hbs b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.hbs new file mode 100644 index 00000000000..7727762884d --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.hbs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.js b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.js new file mode 100644 index 00000000000..f1abb03dfac --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/components/styleguide/chat-thread-original-message.js @@ -0,0 +1,6 @@ +import Component from "@glimmer/component"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; + +export default class ChatStyleguideChatThreadOriginalMessage extends Component { + message = fabricators.message(); +} diff --git a/plugins/chat/assets/javascripts/discourse/lib/fabricators.js b/plugins/chat/assets/javascripts/discourse/lib/fabricators.js new file mode 100644 index 00000000000..9e483f47d4d --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/lib/fabricators.js @@ -0,0 +1,170 @@ +/* +Fabricators are used to create fake data for testing purposes. +The following fabricators are available in lib folder to allow +styleguide to use them, and eventually to generate dummy data +in a placeholder component. It should not be used for any other case. +*/ + +import ChatChannel, { + CHANNEL_STATUSES, + CHATABLE_TYPES, +} from "discourse/plugins/chat/discourse/models/chat-channel"; +import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; +import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread"; +import ChatDirectMessage from "discourse/plugins/chat/discourse/models/chat-direct-message"; +import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-message-reaction"; +import User from "discourse/models/user"; +import Bookmark from "discourse/models/bookmark"; +import Category from "discourse/models/category"; + +let sequence = 0; + +function messageFabricator(args = {}) { + const channel = args.channel || channelFabricator(); + + const message = ChatMessage.create( + channel, + Object.assign( + { + id: args.id || sequence++, + user: args.user || userFabricator(), + message: + args.message || + "@discobot **abc**defghijklmnopqrstuvwxyz [discourse](discourse.org) :rocket: ", + created_at: args.created_at || moment(), + }, + args + ) + ); + + const excerptLength = 50; + const text = message.message.toString(); + if (text.length <= excerptLength) { + message.excerpt = text; + } else { + message.excerpt = text.slice(0, excerptLength) + "..."; + } + + message.cook(); + + return message; +} + +function channelFabricator(args = {}) { + const id = args.id || sequence++; + + return ChatChannel.create( + Object.assign( + { + id, + chatable_type: + args.chatable?.type || + args.chatable_type || + CHATABLE_TYPES.categoryChannel, + last_message_sent_at: args.last_message_sent_at, + chatable_id: args.chatable?.id || args.chatable_id, + title: args.title || "General", + description: args.description, + chatable: args.chatable || categoryFabricator(), + status: CHANNEL_STATUSES.open, + }, + args + ) + ); +} + +function categoryFabricator(args = {}) { + return Category.create({ + id: args.id || sequence++, + color: args.color || "D56353", + read_restricted: false, + name: args.name || "General", + slug: args.slug || "general", + }); +} + +function directMessageFabricator(args = {}) { + return ChatDirectMessage.create({ + id: args.id || sequence++, + users: args.users || [userFabricator(), userFabricator()], + }); +} + +function directMessageChannelFabricator(args = {}) { + const directMessage = + args.chatable || + directMessageFabricator({ + id: args.chatable_id || sequence++, + }); + + return channelFabricator( + Object.assign(args, { + chatable_type: CHATABLE_TYPES.directMessageChannel, + chatable_id: directMessage.id, + chatable: directMessage, + }) + ); +} + +function userFabricator(args = {}) { + return User.create({ + id: args.id || sequence++, + username: args.username || "hawk", + name: args.name, + avatar_template: "/letter_avatar_proxy/v3/letter/t/41988e/{size}.png", + }); +} + +function bookmarkFabricator(args = {}) { + return Bookmark.create({ + id: args.id || sequence++, + }); +} + +function threadFabricator(args = {}) { + const channel = args.channel || channelFabricator(); + return ChatThread.create(channel, { + id: args.id || sequence++, + original_message: args.original_message || messageFabricator({ channel }), + }); +} + +function reactionFabricator(args = {}) { + return ChatMessageReaction.create({ + count: args.count || 1, + users: args.users || [userFabricator()], + emoji: args.emoji || "heart", + reacted: args.reacted || false, + }); +} + +function uploadFabricator() { + return { + extension: "jpeg", + filesize: 126177, + height: 800, + human_filesize: "123 KB", + id: 202, + original_filename: "avatar.PNG.jpg", + retain_hours: null, + short_path: "/images/avatar.png", + short_url: "upload://yoj8pf9DdIeHRRULyw7i57GAYdz.jpeg", + thumbnail_height: 320, + thumbnail_width: 690, + url: "/images/avatar.png", + width: 1920, + }; +} + +export default { + bookmark: bookmarkFabricator, + user: userFabricator, + channel: channelFabricator, + directMessageChannel: directMessageChannelFabricator, + message: messageFabricator, + thread: threadFabricator, + reaction: reactionFabricator, + upload: uploadFabricator, + category: categoryFabricator, + directMessage: directMessageFabricator, +}; diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js index 4735b366dd8..f7a8f0494a5 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js @@ -89,6 +89,7 @@ export default class ChatChannel { @tracked membershipsCount = 0; @tracked archive; @tracked tracking; + @tracked threadingEnabled = false; threadsManager = new ChatThreadsManager(getOwner(this)); messagesManager = new ChatMessagesManager(getOwner(this)); @@ -114,7 +115,10 @@ export default class ChatChannel { this.autoJoinUsers = args.auto_join_users; this.allowChannelWideMentions = args.allow_channel_wide_mentions; this.chatable = this.isDirectMessageChannel - ? ChatDirectMessage.create(args) + ? ChatDirectMessage.create({ + id: args.chatable?.id, + users: args.chatable?.users, + }) : Category.create(args.chatable); this.currentUserMembership = UserChatChannelMembership.create( args.current_user_membership diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-direct-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-direct-message.js index 38e28e3cbd7..3a1cf16211d 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-direct-message.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-direct-message.js @@ -1,5 +1,6 @@ import User from "discourse/models/user"; import { tracked } from "@glimmer/tracking"; +import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel"; export default class ChatDirectMessage { static create(args = {}) { @@ -9,9 +10,11 @@ export default class ChatDirectMessage { @tracked id; @tracked users = null; + type = CHATABLE_TYPES.drectMessageChannel; + constructor(args = {}) { - this.id = args.chatable.id; - this.users = this.#initUsers(args.chatable.users || []); + this.id = args.id; + this.users = this.#initUsers(args.users || []); } #initUsers(users) { diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js b/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js index fb1c6e761e4..86748cab894 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js @@ -12,9 +12,9 @@ export default class ChatMessageReaction { @tracked count = 0; @tracked reacted = false; @tracked users = []; + @tracked emoji; constructor(args = {}) { - this.messageId = args.messageId; this.count = args.count; this.emoji = args.emoji; this.users = this.#initUsersModels(args.users); diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-message.js index 693b29f135a..7f92f9c5bda 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-message.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-message.js @@ -7,7 +7,7 @@ import I18n from "I18n"; import { generateCookFunction } from "discourse/lib/text"; import simpleCategoryHashMentionTransform from "discourse/plugins/chat/discourse/lib/simple-category-hash-mention-transform"; import { getOwner } from "discourse-common/lib/get-owner"; - +import { next } from "@ember/runloop"; export default class ChatMessage { static cookFunction = null; @@ -38,9 +38,10 @@ export default class ChatMessage { @tracked expanded; @tracked bookmark; @tracked userFlagStatus; - @tracked hidden; + @tracked hidden = false; @tracked version = 0; - @tracked edited; + @tracked edited = false; + @tracked editing = false; @tracked chatWebhookEvent = new TrackedObject(); @tracked mentionWarning; @tracked availableFlags; @@ -62,6 +63,7 @@ export default class ChatMessage { this.firstOfResults = args.firstOfResults; this.staged = args.staged; this.edited = args.edited; + this.editing = args.editing; this.availableFlags = args.availableFlags || args.available_flags; this.hidden = args.hidden; this.threadReplyCount = args.threadReplyCount || args.thread_reply_count; @@ -82,10 +84,7 @@ export default class ChatMessage { ? ChatMessage.create(channel, args.in_reply_to || args.replyToMsg) : null); this.channel = channel; - this.reactions = this.#initChatMessageReactionModel( - args.id, - args.reactions - ); + this.reactions = this.#initChatMessageReactionModel(args.reactions); this.uploads = new TrackedArray(args.uploads || []); this.user = this.#initUserModel(args.user); this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null; @@ -138,33 +137,35 @@ export default class ChatMessage { } cook() { - const site = getOwner(this).lookup("service:site"); + next(() => { + const site = getOwner(this).lookup("service:site"); - const markdownOptions = { - featuresOverride: - site.markdown_additional_options?.chat?.limited_pretty_text_features, - markdownItRules: - site.markdown_additional_options?.chat - ?.limited_pretty_text_markdown_rules, - hashtagTypesInPriorityOrder: - site.hashtag_configurations?.["chat-composer"], - hashtagIcons: site.hashtag_icons, - }; - - if (ChatMessage.cookFunction) { - this.cooked = ChatMessage.cookFunction(this.message); - } else { - generateCookFunction(markdownOptions).then((cookFunction) => { - ChatMessage.cookFunction = (raw) => { - return simpleCategoryHashMentionTransform( - cookFunction(raw), - site.categories - ); - }; + const markdownOptions = { + featuresOverride: + site.markdown_additional_options?.chat?.limited_pretty_text_features, + markdownItRules: + site.markdown_additional_options?.chat + ?.limited_pretty_text_markdown_rules, + hashtagTypesInPriorityOrder: + site.hashtag_configurations?.["chat-composer"], + hashtagIcons: site.hashtag_icons, + }; + if (ChatMessage.cookFunction) { this.cooked = ChatMessage.cookFunction(this.message); - }); - } + } else { + generateCookFunction(markdownOptions).then((cookFunction) => { + ChatMessage.cookFunction = (raw) => { + return simpleCategoryHashMentionTransform( + cookFunction(raw), + site.categories + ); + }; + + this.cooked = ChatMessage.cookFunction(this.message); + }); + } + }); } get read() { @@ -306,10 +307,8 @@ export default class ChatMessage { } } - #initChatMessageReactionModel(messageId, reactions = []) { - return reactions.map((reaction) => - ChatMessageReaction.create(Object.assign({ messageId }, reaction)) - ); + #initChatMessageReactionModel(reactions = []) { + return reactions.map((reaction) => ChatMessageReaction.create(reaction)); } #initUserModel(user) { diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js index f39238d2ae0..b01ee19f86b 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-thread.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-thread.js @@ -13,6 +13,10 @@ export const THREAD_STATUSES = { }; export default class ChatThread { + static create(channel, args = {}) { + return new ChatThread(channel, args); + } + @tracked id; @tracked title; @tracked status; diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js index d82b24ec8a0..4be7139cd56 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat-channel-composer.js @@ -4,7 +4,6 @@ import ChatComposer from "./chat-composer"; export default class ChatChannelComposer extends ChatComposer { @service chat; - @service chatChannelThreadComposer; @service router; @action diff --git a/plugins/chat/assets/javascripts/discourse/templates/styleguide/organisms/chat.hbs b/plugins/chat/assets/javascripts/discourse/templates/styleguide/organisms/chat.hbs new file mode 100644 index 00000000000..85c37a190fa --- /dev/null +++ b/plugins/chat/assets/javascripts/discourse/templates/styleguide/organisms/chat.hbs @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml index 534dd98879d..401d0f479d8 100644 --- a/plugins/chat/config/locales/client.en.yml +++ b/plugins/chat/config/locales/client.en.yml @@ -641,3 +641,8 @@ en: chat_notifications_with_unread: one: "Chat notifications - %{count} unread notification" other: "Chat notifications - %{count} unread notifications" + + styleguide: + sections: + chat: + title: Chat diff --git a/plugins/chat/spec/fabricators/chat_fabricator.rb b/plugins/chat/spec/fabricators/chat_fabricator.rb index b860f3f4e11..0fa8cc41f67 100644 --- a/plugins/chat/spec/fabricators/chat_fabricator.rb +++ b/plugins/chat/spec/fabricators/chat_fabricator.rb @@ -49,13 +49,29 @@ Fabricator(:direct_message_channel, from: :chat_channel) do end end -Fabricator(:chat_message, class_name: "Chat::Message") do - chat_channel - user - message "Beep boop" - cooked { |attrs| Chat::Message.cook(attrs[:message]) } - cooked_version Chat::Message::BAKED_VERSION - in_reply_to nil +Fabricator(:chat_message, class_name: "Chat::MessageCreator") do + transient :chat_channel + transient :user + transient :message + transient :in_reply_to + transient :thread + transient :upload_ids + + initialize_with do |transients| + user = transients[:user] || Fabricate(:user) + channel = + transients[:chat_channel] || transients[:thread]&.channel || + transients[:in_reply_to]&.chat_channel || Fabricate(:chat_channel) + + resolved_class.create( + chat_channel: channel, + user: user, + content: transients[:message] || Faker::Lorem.paragraph, + thread_id: transients[:thread]&.id, + in_reply_to_id: transients[:in_reply_to]&.id, + upload_ids: transients[:upload_ids], + ).chat_message + end end Fabricator(:chat_mention, class_name: "Chat::Mention") do diff --git a/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb b/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb new file mode 100644 index 00000000000..33ac3299341 --- /dev/null +++ b/plugins/chat/spec/system/chat/composer/shortcuts/thread_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +RSpec.describe "Chat | composer | shortcuts | thread", type: :system, js: true do + fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) } + fab!(:current_user) { Fabricate(:user) } + fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) } + let(:chat_page) { PageObjects::Pages::Chat.new } + let(:thread_page) { PageObjects::Pages::ChatThread.new } + + before do + SiteSetting.enable_experimental_chat_threaded_discussions = true + chat_system_bootstrap + channel_1.add(current_user) + sign_in(current_user) + end + + describe "ArrowUp" do + let(:thread_1) { message_1.reload.thread } + + context "when there are editable messages" do + let(:last_thread_message) { thread_1.replies.last } + + before do + thread_message_1 = Fabricate(:chat_message, user: current_user, in_reply_to: message_1) + Fabricate(:chat_message, user: current_user, thread: thread_message_1.reload.thread) + end + + it "starts editing the last editable message" do + chat_page.visit_thread(thread_1) + + thread_page.composer.edit_last_message_shortcut + + expect(thread_page.composer_message_details).to have_message(last_thread_message) + expect(thread_page.composer.value).to eq(last_thread_message.message) + end + end + + context "when there are no editable messages" do + before { Fabricate(:chat_message, in_reply_to: message_1) } + + it "does nothing" do + chat_page.visit_thread(thread_1) + + thread_page.composer.edit_last_message_shortcut + + expect(thread_page.composer_message_details).to have_no_message + expect(thread_page.composer.value).to be_blank + end + end + end +end diff --git a/plugins/chat/spec/system/chat_channel_spec.rb b/plugins/chat/spec/system/chat_channel_spec.rb index 1a5c31c5d2d..fdb902c4aad 100644 --- a/plugins/chat/spec/system/chat_channel_spec.rb +++ b/plugins/chat/spec/system/chat_channel_spec.rb @@ -45,9 +45,15 @@ RSpec.describe "Chat channel", type: :system, js: true do chat.visit_channel(channel_1) end - using_session(:tab_1) { channel.send_message("test_message") } + using_session(:tab_1) do |session| + channel.send_message("test_message") + session.quit + end - using_session(:tab_2) { expect(channel).to have_message(text: "test_message") } + using_session(:tab_2) do |session| + expect(channel).to have_message(text: "test_message") + session.quit + end end end diff --git a/plugins/chat/spec/system/edited_message_spec.rb b/plugins/chat/spec/system/edited_message_spec.rb index 930ef5a3f19..ca7bd6f30af 100644 --- a/plugins/chat/spec/system/edited_message_spec.rb +++ b/plugins/chat/spec/system/edited_message_spec.rb @@ -23,11 +23,12 @@ RSpec.describe "Edited message", type: :system, js: true do it "shows as edited for all users" do chat_page.visit_channel(channel_1) - using_session(:user_1) do + using_session(:user_1) do |session| sign_in(editing_user) chat_page.visit_channel(channel_1) channel_page.edit_message(message_1, "a different message") expect(page).to have_content(I18n.t("js.chat.edited")) + session.quit end expect(page).to have_content(I18n.t("js.chat.edited")) diff --git a/plugins/chat/spec/system/message_notifications_mobile_spec.rb b/plugins/chat/spec/system/message_notifications_mobile_spec.rb index b56a6d25be6..c2dbb806b70 100644 --- a/plugins/chat/spec/system/message_notifications_mobile_spec.rb +++ b/plugins/chat/spec/system/message_notifications_mobile_spec.rb @@ -35,7 +35,10 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") expect(page).to have_no_css( @@ -62,7 +65,10 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".do-not-disturb-background") expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") @@ -77,7 +83,10 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") expect(page).to have_no_css( @@ -92,7 +101,10 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "") expect(page).to have_css( @@ -138,14 +150,20 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: dm_channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "1") expect(page).to have_css( ".chat-channel-row[data-chat-channel-id=\"#{dm_channel_1.id}\"] .chat-channel-unread-indicator", ) - using_session(:user_1) { create_message(channel: dm_channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "2") end @@ -162,7 +180,10 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile ".chat-channel-row:nth-child(2)[data-chat-channel-id=\"#{dm_channel_2.id}\"]", ) - using_session(:user_1) { create_message(channel: dm_channel_2, creator: user_2) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_2, creator: user_2) + session.quit + end expect(page).to have_css( ".chat-channel-row:nth-child(1)[data-chat-channel-id=\"#{dm_channel_2.id}\"]", @@ -190,14 +211,20 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile Jobs.run_immediately! visit("/chat") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "") expect(page).to have_css( ".chat-channel-row[data-chat-channel-id=\"#{channel_1.id}\"] .chat-channel-unread-indicator", ) - using_session(:user_1) { create_message(channel: dm_channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_1, creator: user_1) + session.quit + end expect(page).to have_css( ".chat-channel-row[data-chat-channel-id=\"#{dm_channel_1.id}\"] .chat-channel-unread-indicator", diff --git a/plugins/chat/spec/system/message_notifications_with_sidebar_spec.rb b/plugins/chat/spec/system/message_notifications_with_sidebar_spec.rb index 9f2fb332f7e..3617c02193b 100644 --- a/plugins/chat/spec/system/message_notifications_with_sidebar_spec.rb +++ b/plugins/chat/spec/system/message_notifications_with_sidebar_spec.rb @@ -34,7 +34,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "doesn't show anything" do visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") expect(page).to have_no_css(".sidebar-row.channel-#{channel_1.id}") @@ -59,7 +62,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d Jobs.run_immediately! visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".do-not-disturb-background") expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") @@ -72,7 +78,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "doesn't show anything" do visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") expect(page).to have_no_css(".sidebar-row.channel-#{channel_1.id} .unread") @@ -91,7 +100,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "doesn't show any indicator on chat-header-icon" do visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css(".chat-header-icon .chat-channel-unread-indicator") end @@ -109,7 +121,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "doesn't show any indicator on chat-header-icon" do visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_no_css( ".chat-header-icon .chat-channel-unread-indicator.urgent", @@ -137,7 +152,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "correctly renders notifications" do visit("/") - using_session(:user_1) { create_message(channel: channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "") expect(page).to have_css(".sidebar-row.channel-#{channel_1.id} .unread") @@ -178,12 +196,18 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d context "when a message is created" do it "correctly renders notifications" do visit("/") - using_session(:user_1) { create_message(channel: dm_channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "1") expect(page).to have_css(".sidebar-row.channel-#{dm_channel_1.id} .icon.urgent") - using_session(:user_1) { create_message(channel: dm_channel_1, creator: user_1) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_1, creator: user_1) + session.quit + end expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "2") end @@ -198,7 +222,10 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d "#sidebar-section-content-chat-dms .sidebar-section-link-wrapper:nth-child(2) .channel-#{dm_channel_2.id}", ) - using_session(:user_1) { create_message(channel: dm_channel_2, creator: user_2) } + using_session(:user_1) do |session| + create_message(channel: dm_channel_2, creator: user_2) + session.quit + end expect(page).to have_css( "#sidebar-section-content-chat-dms .sidebar-section-link-wrapper:nth-child(1) .channel-#{dm_channel_2.id}", @@ -237,12 +264,13 @@ RSpec.describe "Message notifications - with sidebar", type: :system, js: true d session.quit end - using_session(:current_user) do + using_session(:current_user) do |session| expect(page).to have_css(".sidebar-row.channel-#{dm_channel_1.id} .icon.urgent") expect(page).to have_css( ".chat-header-icon .chat-channel-unread-indicator", text: "1", ) + session.quit end end end diff --git a/plugins/chat/spec/system/page_objects/chat/chat.rb b/plugins/chat/spec/system/page_objects/chat/chat.rb index 96cb18c7d3d..97d5a3feff4 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat.rb @@ -17,8 +17,8 @@ module PageObjects visit("/chat") end - def visit_channel(channel, mobile: false) - visit(channel.url + (mobile ? "?mobile_view=1" : "")) + def visit_channel(channel) + visit(channel.url) has_no_css?(".chat-channel--not-loaded-once") has_no_css?(".chat-skeleton") end diff --git a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb index 27ebd657504..181a4a1fdc5 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb @@ -3,6 +3,15 @@ module PageObjects module Pages class ChatThread < PageObjects::Pages::Base + def composer + @composer ||= PageObjects::Components::Chat::Composer.new(".chat-thread") + end + + def composer_message_details + @composer_message_details ||= + PageObjects::Components::Chat::ComposerMessageDetails.new(".chat-thread") + end + def header find(".chat-thread__header") end diff --git a/plugins/chat/spec/system/page_objects/chat/components/composer.rb b/plugins/chat/spec/system/page_objects/chat/components/composer.rb new file mode 100644 index 00000000000..98fe8fc8225 --- /dev/null +++ b/plugins/chat/spec/system/page_objects/chat/components/composer.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module PageObjects + module Components + module Chat + class Composer < PageObjects::Components::Base + attr_reader :context + + SELECTOR = ".chat-composer__wrapper" + + def initialize(context) + @context = context + end + + def input + find(context).find(SELECTOR).find(".chat-composer__input") + end + + def value + input.value + end + + def reply_to_last_message_shortcut + input.send_keys(%i[shift arrow_up]) + end + + def edit_last_message_shortcut + input.send_keys(%i[arrow_up]) + end + end + end + end +end diff --git a/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb b/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb new file mode 100644 index 00000000000..c064b0cec10 --- /dev/null +++ b/plugins/chat/spec/system/page_objects/chat/components/composer_message_details.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module PageObjects + module Components + module Chat + class ComposerMessageDetails < PageObjects::Components::Base + attr_reader :context + + SELECTOR = ".chat-composer-message-details" + + def initialize(context) + @context = context + end + + def has_message?(message) + find(context).find(SELECTOR + "[data-id=\"#{message.id}\"]") + end + + def has_no_message? + find(context).has_no_css?(SELECTOR) + end + end + end + end +end diff --git a/plugins/chat/spec/system/reply_to_message/drawer_spec.rb b/plugins/chat/spec/system/reply_to_message/drawer_spec.rb index 8678063e512..6cc68d809d5 100644 --- a/plugins/chat/spec/system/reply_to_message/drawer_spec.rb +++ b/plugins/chat/spec/system/reply_to_message/drawer_spec.rb @@ -87,7 +87,7 @@ RSpec.describe "Reply to message - channel - drawer", type: :system, js: true do expect(page).to have_selector( ".chat-channel .chat-reply__excerpt", - text: original_message.message, + text: original_message.excerpt, ) channel_page.fill_composer("reply to message") diff --git a/plugins/chat/spec/system/reply_to_message/full_page_spec.rb b/plugins/chat/spec/system/reply_to_message/full_page_spec.rb index 1ec1101be0a..d2e26192c40 100644 --- a/plugins/chat/spec/system/reply_to_message/full_page_spec.rb +++ b/plugins/chat/spec/system/reply_to_message/full_page_spec.rb @@ -91,7 +91,7 @@ RSpec.describe "Reply to message - channel - full page", type: :system, js: true expect(page).to have_selector( ".chat-channel .chat-reply__excerpt", - text: original_message.message, + text: original_message.excerpt, ) channel_page.fill_composer("reply to message") diff --git a/plugins/chat/spec/system/reply_to_message/mobile_spec.rb b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb index 9a1b1fc9cf3..e5c188753fe 100644 --- a/plugins/chat/spec/system/reply_to_message/mobile_spec.rb +++ b/plugins/chat/spec/system/reply_to_message/mobile_spec.rb @@ -97,7 +97,7 @@ RSpec.describe "Reply to message - channel - mobile", type: :system, js: true, m expect(page).to have_selector( ".chat-channel .chat-reply__excerpt", - text: original_message.message, + text: original_message.excerpt, ) channel_page.fill_composer("reply to message") diff --git a/plugins/chat/spec/system/single_thread_spec.rb b/plugins/chat/spec/system/single_thread_spec.rb index 3300e83a7c5..1af832312e1 100644 --- a/plugins/chat/spec/system/single_thread_spec.rb +++ b/plugins/chat/spec/system/single_thread_spec.rb @@ -161,15 +161,17 @@ describe "Single thread in side panel", type: :system, js: true do expect(thread_page).to have_message(thread_id: thread.id, text: "the other user message") end - using_session(:tab_1) do + using_session(:tab_1) do |session| expect(side_panel).to have_open_thread(thread) expect(thread_page).to have_message(thread_id: thread.id, text: "the other user message") thread_page.send_message("this is a test message") expect(thread_page).to have_message(thread_id: thread.id, text: "this is a test message") + session.quit end - using_session(:tab_2) do + using_session(:tab_2) do |session| expect(thread_page).to have_message(thread_id: thread.id, text: "this is a test message") + session.quit end end diff --git a/plugins/chat/spec/system/unfollow_dm_channel_spec.rb b/plugins/chat/spec/system/unfollow_dm_channel_spec.rb index aff4a419009..10e08bc02fd 100644 --- a/plugins/chat/spec/system/unfollow_dm_channel_spec.rb +++ b/plugins/chat/spec/system/unfollow_dm_channel_spec.rb @@ -22,12 +22,13 @@ RSpec.describe "Unfollow dm channel", type: :system, js: true do expect(page).to have_no_css(".channel-#{dm_channel_1.id}") - using_session(:user_1) do + using_session(:user_1) do |session| text = "this is fine" sign_in(other_user) chat_page.visit_channel(dm_channel_1) chat_channel_page.send_message(text) expect(chat_channel_page).to have_message(text: text) + session.quit end expect(page).to have_css(".channel-#{dm_channel_1.id} .urgent") diff --git a/plugins/chat/spec/system/user_menu_notifications/sidebar_spec.rb b/plugins/chat/spec/system/user_menu_notifications/sidebar_spec.rb index 3bfa6ad19ac..9e57c4df6ae 100644 --- a/plugins/chat/spec/system/user_menu_notifications/sidebar_spec.rb +++ b/plugins/chat/spec/system/user_menu_notifications/sidebar_spec.rb @@ -198,13 +198,14 @@ RSpec.describe "User menu notifications | sidebar", type: :system, js: true do channel.send_message("this is fine @#{other_user.username}") find(".invite-link", wait: 5).click - using_session(:user_1) do + using_session(:user_1) do |session| sign_in(other_user) visit("/") find(".header-dropdown-toggle.current-user").click expect(find("#user-menu-button-chat-notifications")).to have_content(1) expect(find("#quick-access-all-notifications")).to have_css(".chat-invitation.unread") + session.quit end end end diff --git a/plugins/chat/test/javascripts/components/chat-channel-archive-modal-inner-test.js b/plugins/chat/test/javascripts/components/chat-channel-archive-modal-inner-test.js index 02362286623..6995574c416 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-archive-modal-inner-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-archive-modal-inner-test.js @@ -1,5 +1,5 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; import { render } from "@ember/test-helpers"; @@ -13,7 +13,7 @@ module( test("channel title is escaped in instructions correctly", async function (assert) { this.set( "channel", - fabricators.chatChannel({ + fabricators.channel({ title: ``, }) ); diff --git a/plugins/chat/test/javascripts/components/chat-channel-card-test.js b/plugins/chat/test/javascripts/components/chat-channel-card-test.js index aff694db1c2..6efa762562b 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-card-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-card-test.js @@ -1,7 +1,7 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { exists, query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { render } from "@ember/test-helpers"; import { module, test } from "qunit"; import I18n from "I18n"; @@ -10,7 +10,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); this.channel.description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; }); diff --git a/plugins/chat/test/javascripts/components/chat-channel-delete-modal-inner-test.js b/plugins/chat/test/javascripts/components/chat-channel-delete-modal-inner-test.js index da9a92c4855..72a9d84d3be 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-delete-modal-inner-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-delete-modal-inner-test.js @@ -1,5 +1,5 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; import { render } from "@ember/test-helpers"; @@ -13,7 +13,7 @@ module( test("channel title is escaped in instructions correctly", async function (assert) { this.set( "channel", - fabricators.chatChannel({ + fabricators.channel({ title: ``, }) ); diff --git a/plugins/chat/test/javascripts/components/chat-channel-leave-btn-test.js b/plugins/chat/test/javascripts/components/chat-channel-leave-btn-test.js index 672f480333d..09cee5fd4b8 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-leave-btn-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-leave-btn-test.js @@ -5,7 +5,7 @@ import hbs from "htmlbars-inline-precompile"; import pretender from "discourse/tests/helpers/create-pretender"; import I18n from "I18n"; import { module, test } from "qunit"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) { setupRenderingTest(hooks); @@ -13,7 +13,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) { test("accepts an optional onLeaveChannel callback", async function (assert) { this.foo = 1; this.onLeaveChannel = () => (this.foo = 2); - this.channel = fabricators.directMessageChatChannel({ users: [{ id: 1 }] }); + this.channel = fabricators.directMessageChannel(); await render( hbs`` @@ -29,7 +29,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) { }); test("has a specific title for direct message channel", async function (assert) { - this.channel = fabricators.directMessageChatChannel(); + this.channel = fabricators.directMessageChannel(); await render(hbs``); @@ -38,7 +38,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) { }); test("has a specific title for message channel", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render(hbs``); @@ -48,7 +48,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) { test("is not visible on mobile", async function (assert) { this.site.mobileView = true; - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render(hbs``); diff --git a/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js b/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js index d9420e6f3e2..09538f4b3d4 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js @@ -1,7 +1,7 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { exists } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; @@ -10,7 +10,7 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) { test("displays last message sent at", async function (assert) { let lastMessageSentAt = moment().subtract(1, "day").format(); - this.channel = fabricators.directMessageChatChannel({ + this.channel = fabricators.directMessageChannel({ last_message_sent_at: lastMessageSentAt, }); @@ -28,7 +28,7 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) { }); test("unreadIndicator", async function (assert) { - this.channel = fabricators.directMessageChatChannel(); + this.channel = fabricators.directMessageChannel(); this.channel.tracking.unreadCount = 1; this.unreadIndicator = true; diff --git a/plugins/chat/test/javascripts/components/chat-channel-preview-card-test.js b/plugins/chat/test/javascripts/components/chat-channel-preview-card-test.js index 54a65348365..0c4c7d0c350 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-preview-card-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-preview-card-test.js @@ -3,7 +3,7 @@ import { exists, query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; import { render } from "@ember/test-helpers"; import { module, test } from "qunit"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module( "Discourse Chat | Component | chat-channel-preview-card", @@ -11,10 +11,7 @@ module( setupRenderingTest(hooks); hooks.beforeEach(function () { - this.set( - "channel", - fabricators.chatChannel({ chatable_type: "Category" }) - ); + this.set("channel", fabricators.channel({ chatable_type: "Category" })); this.channel.description = "Important stuff is announced here."; this.channel.title = "announcements"; diff --git a/plugins/chat/test/javascripts/components/chat-channel-row-test.js b/plugins/chat/test/javascripts/components/chat-channel-row-test.js index 7920e2fe7d5..14784ab6de1 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-row-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-row-test.js @@ -2,14 +2,14 @@ import { module, test } from "qunit"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { render } from "@ember/test-helpers"; import { hbs } from "ember-cli-htmlbars"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module("Discourse Chat | Component | chat-channel-row", function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { - this.categoryChatChannel = fabricators.chatChannel(); - this.directMessageChatChannel = fabricators.directMessageChatChannel(); + this.categoryChatChannel = fabricators.channel(); + this.directMessageChannel = fabricators.directMessageChannel(); }); test("links to correct channel", async function (assert) { @@ -47,11 +47,14 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) { }); test("renders correct channel metadata", async function (assert) { + this.categoryChatChannel.lastMessageSentAt = moment().toISOString(); await render(hbs``); assert .dom(".chat-channel-metadata") - .hasText(moment(this.categoryChatChannel.lastMessageSentAt).format("l")); + .hasText( + moment(this.categoryChatChannel.lastMessageSentAt).format("h:mm A") + ); }); test("renders membership toggling button when necessary", async function (assert) { @@ -145,11 +148,14 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) { }); test("user status with direct message channel", async function (assert) { + this.directMessageChannel.chatable = fabricators.directMessage({ + users: [fabricators.user()], + }); const status = { description: "Off to dentist", emoji: "tooth" }; - this.directMessageChatChannel.chatable.users[0].status = status; + this.directMessageChannel.chatable.users[0].status = status; await render( - hbs`` + hbs`` ); assert.dom(".user-status-message").exists(); @@ -157,9 +163,9 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) { test("user status with direct message channel and multiple users", async function (assert) { const status = { description: "Off to dentist", emoji: "tooth" }; - this.directMessageChatChannel.chatable.users[0].status = status; + this.directMessageChannel.chatable.users[0].status = status; - this.directMessageChatChannel.chatable.users.push({ + this.directMessageChannel.chatable.users.push({ id: 2, username: "bill", name: null, @@ -167,7 +173,7 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) { }); await render( - hbs`` + hbs`` ); assert.dom(".user-status-message").doesNotExist(); diff --git a/plugins/chat/test/javascripts/components/chat-channel-status-test.js b/plugins/chat/test/javascripts/components/chat-channel-status-test.js index 933eec276cd..cba9e9a286c 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-status-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-status-test.js @@ -3,7 +3,7 @@ import hbs from "htmlbars-inline-precompile"; import I18n from "I18n"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { CHANNEL_STATUSES, channelStatusIcon, @@ -13,7 +13,7 @@ module("Discourse Chat | Component | chat-channel-status", function (hooks) { setupRenderingTest(hooks); test("renders nothing when channel is opened", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render(hbs``); @@ -21,7 +21,7 @@ module("Discourse Chat | Component | chat-channel-status", function (hooks) { }); test("defaults to long format", async function (assert) { - this.channel = fabricators.chatChannel({ status: CHANNEL_STATUSES.closed }); + this.channel = fabricators.channel({ status: CHANNEL_STATUSES.closed }); await render(hbs``); @@ -31,7 +31,7 @@ module("Discourse Chat | Component | chat-channel-status", function (hooks) { }); test("accepts a format argument", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ status: CHANNEL_STATUSES.archived, }); @@ -45,7 +45,7 @@ module("Discourse Chat | Component | chat-channel-status", function (hooks) { }); test("renders the correct icon", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ status: CHANNEL_STATUSES.archived, }); @@ -56,7 +56,7 @@ module("Discourse Chat | Component | chat-channel-status", function (hooks) { test("renders archive status", async function (assert) { this.currentUser.admin = true; - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ status: CHANNEL_STATUSES.archived, archive_failed: true, }); diff --git a/plugins/chat/test/javascripts/components/chat-channel-title-test.js b/plugins/chat/test/javascripts/components/chat-channel-title-test.js index 7523815867c..40156d4e0b0 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-title-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-title-test.js @@ -1,7 +1,7 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { exists, query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; @@ -10,7 +10,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { setupRenderingTest(hooks); test("category channel", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ chatable_type: CHATABLE_TYPES.categoryChannel, }); @@ -27,7 +27,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { }); test("category channel - escapes title", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ chatable_type: CHATABLE_TYPES.categoryChannel, title: "
evil
", }); @@ -38,7 +38,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { }); test("category channel - read restricted", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ chatable_type: CHATABLE_TYPES.categoryChannel, chatable: { read_restricted: true }, }); @@ -49,7 +49,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { }); test("category channel - not read restricted", async function (assert) { - this.channel = fabricators.chatChannel({ + this.channel = fabricators.channel({ chatable_type: CHATABLE_TYPES.categoryChannel, chatable: { read_restricted: false }, }); @@ -60,7 +60,11 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { }); test("direct message channel - one user", async function (assert) { - this.channel = fabricators.directMessageChatChannel(); + this.channel = fabricators.directMessageChannel({ + chatable: fabricators.directMessage({ + users: [fabricators.user()], + }), + }); await render(hbs``); @@ -77,7 +81,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) { }); test("direct message channel - multiple users", async function (assert) { - const channel = fabricators.directMessageChatChannel(); + const channel = fabricators.directMessageChannel(); channel.chatable.users.push({ id: 2, diff --git a/plugins/chat/test/javascripts/components/chat-composer-message-details-test.js b/plugins/chat/test/javascripts/components/chat-composer-message-details-test.js new file mode 100644 index 00000000000..147d50a89cc --- /dev/null +++ b/plugins/chat/test/javascripts/components/chat-composer-message-details-test.js @@ -0,0 +1,77 @@ +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import hbs from "htmlbars-inline-precompile"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; +import { module, test } from "qunit"; +import { render } from "@ember/test-helpers"; + +module( + "Discourse Chat | Component | chat-composer-message-details", + function (hooks) { + setupRenderingTest(hooks); + + test("data-id attribute", async function (assert) { + this.message = fabricators.message(); + + await render( + hbs`` + ); + + assert + .dom(".chat-composer-message-details") + .hasAttribute("data-id", this.message.id.toString()); + }); + + test("editing a message has the pencil icon", async function (assert) { + this.message = fabricators.message({ editing: true }); + + await render( + hbs`` + ); + + assert.dom(".chat-composer-message-details .d-icon-pencil-alt").exists(); + }); + + test("replying to a message has the reply icon", async function (assert) { + const firstMessage = fabricators.message(); + this.message = fabricators.message({ inReplyTo: firstMessage }); + + await render( + hbs`` + ); + + assert.dom(".chat-composer-message-details .d-icon-reply").exists(); + }); + + test("displays user avatar", async function (assert) { + this.message = fabricators.message(); + + await render( + hbs`` + ); + + assert + .dom(".chat-composer-message-details .chat-user-avatar .avatar") + .hasAttribute("title", this.message.user.username); + }); + + test("displays message excerpt", async function (assert) { + this.message = fabricators.message(); + + await render( + hbs`` + ); + + assert.dom(".chat-reply__excerpt").hasText(this.message.excerpt); + }); + + test("displays user’s username", async function (assert) { + this.message = fabricators.message(); + + await render( + hbs`` + ); + + assert.dom(".chat-reply__username").hasText(this.message.user.username); + }); + } +); diff --git a/plugins/chat/test/javascripts/components/chat-message-avatar-test.js b/plugins/chat/test/javascripts/components/chat-message-avatar-test.js index dff366aec8b..59a61329262 100644 --- a/plugins/chat/test/javascripts/components/chat-message-avatar-test.js +++ b/plugins/chat/test/javascripts/components/chat-message-avatar-test.js @@ -4,13 +4,13 @@ import { exists, query } from "discourse/tests/helpers/qunit-helpers"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module("Discourse Chat | Component | chat-message-avatar", function (hooks) { setupRenderingTest(hooks); test("chat_webhook_event", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { chat_webhook_event: { emoji: ":heart:" }, }); @@ -20,7 +20,7 @@ module("Discourse Chat | Component | chat-message-avatar", function (hooks) { }); test("user", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, }); diff --git a/plugins/chat/test/javascripts/components/chat-message-info-test.js b/plugins/chat/test/javascripts/components/chat-message-info-test.js index f633d71645f..a99f9db6548 100644 --- a/plugins/chat/test/javascripts/components/chat-message-info-test.js +++ b/plugins/chat/test/javascripts/components/chat-message-info-test.js @@ -6,13 +6,13 @@ import I18n from "I18n"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module("Discourse Chat | Component | chat-message-info", function (hooks) { setupRenderingTest(hooks); test("chat_webhook_event", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { chat_webhook_event: { username: "discobot" }, }); @@ -29,7 +29,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("user", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, }); @@ -42,7 +42,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("date", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, created_at: moment(), }); @@ -53,7 +53,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("bookmark (with reminder)", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, bookmark: Bookmark.create({ reminder_at: moment(), @@ -69,7 +69,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("bookmark (no reminder)", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, bookmark: Bookmark.create({ name: "some name", @@ -83,7 +83,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { test("user status", async function (assert) { const status = { description: "off to dentist", emoji: "tooth" }; - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { status }, }); @@ -93,7 +93,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("reviewable", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, user_flag_status: 0, }); @@ -105,7 +105,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { I18n.t("chat.you_flagged") ); - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, reviewable_id: 1, }); @@ -119,7 +119,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("with username classes", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot", admin: true, @@ -139,7 +139,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) { }); test("without username classes", async function (assert) { - this.message = ChatMessage.create(fabricators.chatChannel(), { + this.message = ChatMessage.create(fabricators.channel(), { user: { username: "discobot" }, }); diff --git a/plugins/chat/test/javascripts/components/chat-message-move-to-channel-modal-inner-test.js b/plugins/chat/test/javascripts/components/chat-message-move-to-channel-modal-inner-test.js index ebf72124b06..e40a49f7819 100644 --- a/plugins/chat/test/javascripts/components/chat-message-move-to-channel-modal-inner-test.js +++ b/plugins/chat/test/javascripts/components/chat-message-move-to-channel-modal-inner-test.js @@ -1,5 +1,5 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; import { render } from "@ember/test-helpers"; @@ -13,7 +13,7 @@ module( test("channel title is escaped in instructions correctly", async function (assert) { this.set( "channel", - fabricators.chatChannel({ title: "" }) + fabricators.channel({ title: "" }) ); this.set("chat", { publicChannels: [this.channel] }); this.set("selectedMessageIds", [1]); diff --git a/plugins/chat/test/javascripts/components/chat-message-test.js b/plugins/chat/test/javascripts/components/chat-message-test.js index 094e0d75538..ca0b49b588a 100644 --- a/plugins/chat/test/javascripts/components/chat-message-test.js +++ b/plugins/chat/test/javascripts/components/chat-message-test.js @@ -55,7 +55,6 @@ module("Discourse Chat | Component | chat-message", function (hooks) { const template = hbs` diff --git a/plugins/chat/test/javascripts/components/chat-replying-indicator-test.js b/plugins/chat/test/javascripts/components/chat-replying-indicator-test.js index e63fa273e39..2d4475ae6f1 100644 --- a/plugins/chat/test/javascripts/components/chat-replying-indicator-test.js +++ b/plugins/chat/test/javascripts/components/chat-replying-indicator-test.js @@ -1,7 +1,7 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { query } from "discourse/tests/helpers/qunit-helpers"; import hbs from "htmlbars-inline-precompile"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; import { @@ -87,7 +87,7 @@ module( }); test("displays indicator when 2 or 3 users are replying", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render( hbs`` @@ -102,7 +102,7 @@ module( }); test("displays indicator when 3 users are replying", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render( hbs`` @@ -118,7 +118,7 @@ module( }); test("displays indicator when more than 3 users are replying", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render( hbs`` @@ -135,7 +135,7 @@ module( }); test("filters current user from list of repliers", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); await render( hbs`` diff --git a/plugins/chat/test/javascripts/components/chat-retention-reminder-text-test.js b/plugins/chat/test/javascripts/components/chat-retention-reminder-text-test.js index a5d8468d99c..52acf8bd1d8 100644 --- a/plugins/chat/test/javascripts/components/chat-retention-reminder-text-test.js +++ b/plugins/chat/test/javascripts/components/chat-retention-reminder-text-test.js @@ -3,7 +3,7 @@ import hbs from "htmlbars-inline-precompile"; import I18n from "I18n"; import { module, test } from "qunit"; import { render } from "@ember/test-helpers"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module( "Discourse Chat | Component | chat-retention-reminder-text", @@ -11,7 +11,7 @@ module( setupRenderingTest(hooks); test("when setting is set on 0", async function (assert) { - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); this.siteSettings.chat_channel_retention_days = 0; await render( @@ -25,7 +25,7 @@ module( test("when channel is a public channel", async function (assert) { const count = 10; - this.channel = fabricators.chatChannel(); + this.channel = fabricators.channel(); this.siteSettings.chat_channel_retention_days = count; await render( @@ -39,7 +39,7 @@ module( test("when channel is a DM channel", async function (assert) { const count = 10; - this.channel = fabricators.directMessageChatChannel(); + this.channel = fabricators.directMessageChannel(); this.siteSettings.chat_dm_retention_days = count; await render( diff --git a/plugins/chat/test/javascripts/components/direct-message-creator-test.js b/plugins/chat/test/javascripts/components/direct-message-creator-test.js index 050df9ebdd7..2122da0f60a 100644 --- a/plugins/chat/test/javascripts/components/direct-message-creator-test.js +++ b/plugins/chat/test/javascripts/components/direct-message-creator-test.js @@ -4,7 +4,7 @@ import hbs from "htmlbars-inline-precompile"; import { exists, query } from "discourse/tests/helpers/qunit-helpers"; import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel"; import { Promise } from "rsvp"; -import fabricators from "../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; import { module, test } from "qunit"; function mockChat(context, options = {}) { @@ -15,7 +15,7 @@ function mockChat(context, options = {}) { }); }; mock.getDmChannelForUsernames = () => { - return Promise.resolve({ chat_channel: fabricators.chatChannel() }); + return Promise.resolve({ chat_channel: fabricators.channel() }); }; return mock; } @@ -114,7 +114,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) { await fillIn(".filter-usernames", "hawk"); assert.strictEqual(query(".filter-usernames").value, "hawk"); - this.set("channel", fabricators.chatChannel()); + this.set("channel", fabricators.channel()); this.set("channel", ChatChannel.createDirectMessageChannelDraft()); assert.strictEqual(query(".filter-usernames").value, ""); diff --git a/plugins/chat/test/javascripts/helpers/fabricator.js b/plugins/chat/test/javascripts/helpers/fabricator.js deleted file mode 100644 index 8a513c48128..00000000000 --- a/plugins/chat/test/javascripts/helpers/fabricator.js +++ /dev/null @@ -1,21 +0,0 @@ -import { cloneJSON } from "discourse-common/lib/object"; - -// heavily inspired by https://github.com/travelperk/fabricator -export function Fabricator(Model, attributes = {}) { - return (opts) => fabricate(Model, attributes, opts); -} - -function fabricate(Model, attributes, opts = {}) { - if (typeof attributes === "function") { - return attributes(); - } - - const extendedModel = cloneJSON({ ...attributes, ...opts }); - const props = {}; - - for (const [key, value] of Object.entries(extendedModel)) { - props[key] = typeof value === "function" ? value() : value; - } - - return Model.create(props); -} diff --git a/plugins/chat/test/javascripts/helpers/fabricators.js b/plugins/chat/test/javascripts/helpers/fabricators.js deleted file mode 100644 index 07cf409ee24..00000000000 --- a/plugins/chat/test/javascripts/helpers/fabricators.js +++ /dev/null @@ -1,60 +0,0 @@ -import ChatChannel, { - CHATABLE_TYPES, -} from "discourse/plugins/chat/discourse/models/chat-channel"; -import EmberObject from "@ember/object"; -import { Fabricator } from "./fabricator"; -import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; - -const userFabricator = Fabricator(EmberObject, { - id: 1, - username: "hawk", - name: null, - avatar_template: "/letter_avatar_proxy/v3/letter/t/41988e/{size}.png", -}); - -const categoryChatableFabricator = Fabricator(EmberObject, { - id: 1, - color: "D56353", - read_restricted: false, - name: "My category", -}); - -const directChannelChatableFabricator = Fabricator(EmberObject, { - users: [userFabricator({ id: 1, username: "bob" })], -}); - -export default { - chatChannel: Fabricator(ChatChannel, { - id: 1, - chatable_type: CHATABLE_TYPES.categoryChannel, - status: "open", - title: "My category title", - name: "My category name", - chatable: categoryChatableFabricator(), - last_message_sent_at: "2021-11-08T21:26:05.710Z", - allow_channel_wide_mentions: true, - message_bus_last_ids: { - new_mentions: 0, - new_messages: 0, - }, - }), - - chatChannelMessage: Fabricator(ChatMessage, { - id: 1, - chat_channel_id: 1, - user_id: 1, - cooked: "This is a test message", - }), - - directMessageChatChannel: Fabricator(ChatChannel, { - id: 1, - chatable_type: CHATABLE_TYPES.directMessageChannel, - status: "open", - chatable: directChannelChatableFabricator(), - last_message_sent_at: "2021-11-08T21:26:05.710Z", - message_bus_last_ids: { - new_mentions: 0, - new_messages: 0, - }, - }), -}; diff --git a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js b/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js index f6acc11a457..d5d11a8c1c2 100644 --- a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js +++ b/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js @@ -3,21 +3,20 @@ import hbs from "htmlbars-inline-precompile"; import { render } from "@ember/test-helpers"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; import { query } from "discourse/tests/helpers/qunit-helpers"; -import fabricators from "../../helpers/fabricators"; -import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; module("Discourse Chat | Unit | Helpers | format-chat-date", function (hooks) { setupRenderingTest(hooks); test("link to chat message", async function (assert) { - const channel = fabricators.chatChannel(); - this.message = ChatMessage.create(channel, { - id: 1, - chat_channel_id: channel.id, - }); + const channel = fabricators.channel(); + this.message = fabricators.message({ channel }); await render(hbs`{{format-chat-date this.message}}`); - assert.equal(query(".chat-time").getAttribute("href"), "/chat/c/-/1/1"); + assert.equal( + query(".chat-time").getAttribute("href"), + `/chat/c/-/${channel.id}/${this.message.id}` + ); }); }); diff --git a/plugins/chat/test/javascripts/unit/services/chat-guardian-test.js b/plugins/chat/test/javascripts/unit/services/chat-guardian-test.js index 05fd86d5041..136701eea8d 100644 --- a/plugins/chat/test/javascripts/unit/services/chat-guardian-test.js +++ b/plugins/chat/test/javascripts/unit/services/chat-guardian-test.js @@ -1,7 +1,7 @@ import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { test } from "qunit"; import { set } from "@ember/object"; -import fabricators from "../../helpers/fabricators"; +import fabricators from "discourse/plugins/chat/discourse/lib/fabricators"; acceptance("Discourse Chat | Unit | Service | chat-guardian", function (needs) { needs.hooks.beforeEach(function () { @@ -69,7 +69,7 @@ acceptance("Discourse Chat | Unit | Service | chat-guardian", function (needs) { }); test("#canArchiveChannel", async function (assert) { - const channel = fabricators.chatChannel(); + const channel = fabricators.channel(); set(this.currentUser, "has_chat_enabled", true); set(this.currentUser, "admin", true); diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs new file mode 100644 index 00000000000..87963a352ae --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
\ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.js b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.js new file mode 100644 index 00000000000..d2571b9a083 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/component.js @@ -0,0 +1,3 @@ +import Component from "@glimmer/component"; + +export default class StyleguideComponent extends Component {} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.hbs b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.hbs new file mode 100644 index 00000000000..42d3152a89c --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.hbs @@ -0,0 +1,5 @@ + + + {{yield}} + +
\ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.js b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.js new file mode 100644 index 00000000000..f7fc07ef4c8 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls.js @@ -0,0 +1,3 @@ +import Component from "@glimmer/component"; + +export default class StyleguideControls extends Component {} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/row.hbs b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/row.hbs new file mode 100644 index 00000000000..59f0c996c77 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/row.hbs @@ -0,0 +1,6 @@ + + {{@name}} + + {{yield}} + + \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.hbs b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.hbs new file mode 100644 index 00000000000..e4c7fc4720d --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.js b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.js new file mode 100644 index 00000000000..5a3c13f4268 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/styleguide/controls/toggle.js @@ -0,0 +1,3 @@ +import Component from "@glimmer/component"; + +export default class StyleguideControlsToggle extends Component {} diff --git a/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.hbs b/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.hbs new file mode 100644 index 00000000000..cc147e30817 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.hbs @@ -0,0 +1 @@ +Toggle color \ No newline at end of file diff --git a/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.js b/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.js new file mode 100644 index 00000000000..e06f93cff84 --- /dev/null +++ b/plugins/styleguide/assets/javascripts/discourse/components/toggle-color-mode.js @@ -0,0 +1,45 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import { tracked } from "@glimmer/tracking"; + +const DARK = "dark"; +const LIGHT = "light"; + +function colorSchemeOverride(type) { + const lightScheme = document.querySelector("link.light-scheme"); + const darkScheme = document.querySelector("link.dark-scheme"); + + if (!lightScheme || !darkScheme) { + return; + } + + switch (type) { + case DARK: + lightScheme.media = "none"; + darkScheme.media = "all"; + break; + case LIGHT: + lightScheme.media = "all"; + darkScheme.media = "none"; + break; + } +} + +export default class ToggleColorMode extends Component { + @service keyValueStore; + + @tracked colorSchemeOverride = this.default; + + get default() { + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? DARK + : LIGHT; + } + + @action + toggle() { + this.colorSchemeOverride = this.colorSchemeOverride === DARK ? LIGHT : DARK; + colorSchemeOverride(this.colorSchemeOverride); + } +} diff --git a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs index 4961f46af16..7e9b054a118 100644 --- a/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs +++ b/plugins/styleguide/assets/javascripts/discourse/templates/styleguide.hbs @@ -1,5 +1,6 @@
+ {{#each this.categories as |c|}}
  • {{i18n diff --git a/plugins/styleguide/assets/stylesheets/styleguide.scss b/plugins/styleguide/assets/stylesheets/styleguide.scss index 432c58c766e..e92c541f407 100644 --- a/plugins/styleguide/assets/stylesheets/styleguide.scss +++ b/plugins/styleguide/assets/stylesheets/styleguide.scss @@ -73,6 +73,36 @@ .rendered { width: 100%; + position: relative; + + .component { + padding: 2rem; + border: 2px dotted var(--primary-low); + margin-bottom: 2rem; + } + + .component-properties { + width: 100%; + + &__cell { + padding: 0.5rem 0; + + &:first-child { + width: 30%; + } + + textarea, + input { + box-sizing: border-box; + margin: 0; + width: 100%; + } + + textarea { + height: 100px; + } + } + } } margin-bottom: 2em; diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index 9bc48356008..2abc15e12ae 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -101,7 +101,10 @@ module SystemHelpers ENV["TZ"] = timezone - using_session(timezone) { freeze_time(&example) } + using_session(timezone) do |session| + freeze_time(&example) + session.quit + end ENV["TZ"] = previous_browser_timezone end