From add6c671d6a971b72a91b02020c890f200c72aec Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 2 Dec 2022 16:57:35 +0100 Subject: [PATCH] DEV: glimmerify chat-channel-row (#19287) --- .../discourse/components/chat-channel-row.hbs | 31 ++- .../discourse/components/chat-channel-row.js | 110 ++------ .../chat-keyboard-shortcuts-test.js | 2 +- .../acceptance/chat-status-test.js | 8 +- .../test/javascripts/acceptance/chat-test.js | 16 +- .../acceptance/mobile-chat-test.js | 2 +- .../acceptance/user-card-chat-test.js | 2 +- .../components/chat-channel-row-test.js | 251 +++++++++++------- 8 files changed, 222 insertions(+), 200 deletions(-) diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.hbs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.hbs index 4c75b80da70..83776ff9f77 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.hbs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.hbs @@ -1,15 +1,34 @@ - - - + + + - {{#if (and this.options.leaveButton this.channel.isFollowing (not this.site.mobileView))}} + {{#if (and @options.leaveButton @channel.isFollowing this.site.desktopView)}} {{/if}} diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.js index a54012c85b3..e703be69381 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-row.js @@ -1,94 +1,32 @@ -import Component from "@ember/component"; -import I18n from "I18n"; -import discourseComputed from "discourse-common/utils/decorators"; -import { equal } from "@ember/object/computed"; import { inject as service } from "@ember/service"; -import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel"; +import Component from "@glimmer/component"; +import { action } from "@ember/object"; -export default Component.extend({ - tagName: "", - router: service(), - chat: service(), - channel: null, - isDirectMessageRow: equal( - "channel.chatable_type", - CHATABLE_TYPES.directMessageChannel - ), - options: null, +export default class ChatChannelRow extends Component { + @service router; + @service chat; + @service currentUser; + @service site; - didInsertElement() { - this._super(...arguments); + @action + startTrackingStatus() { + this.#firstDirectMessageUser?.trackStatus(); + } - if (this.isDirectMessageRow) { - this.channel.chatable.users[0].trackStatus(); - } - }, + @action + stopTrackingStatus() { + this.#firstDirectMessageUser?.stopTrackingStatus(); + } - willDestroyElement() { - this._super(...arguments); - - if (this.isDirectMessageRow) { - this.channel.chatable.users[0].stopTrackingStatus(); - } - }, - - @discourseComputed( - "channel.id", - "chat.activeChannel.id", - "router.currentRouteName" - ) - active(channelId, activeChannelId, currentRouteName) { + get channelHasUnread() { return ( - currentRouteName?.startsWith("chat.channel") && - channelId === activeChannelId + this.currentUser.get( + `chat_channel_tracking_state.${this.args.channel?.id}.unread_count` + ) > 0 ); - }, + } - @discourseComputed("active", "channel.{id,muted}", "channel.focused") - rowClassNames(active, channel, focused) { - const classes = ["chat-channel-row", `chat-channel-${channel.id}`]; - - if (active) { - classes.push("active"); - } - if (focused) { - classes.push("focused"); - } - if (channel.current_user_membership.muted) { - classes.push("muted"); - } - if (this.options?.leaveButton) { - classes.push("can-leave"); - } - - const channelUnreadCount = - this.currentUser.chat_channel_tracking_state?.[channel.id]?.unread_count; - if (channelUnreadCount > 0) { - classes.push("has-unread"); - } - - return classes.join(" "); - }, - - @discourseComputed( - "isDirectMessageRow", - "channel.chatable.users.[]", - "channel.chatable.users.@each.status" - ) - showUserStatus(isDirectMessageRow) { - return !!( - isDirectMessageRow && - this.channel.chatable.users.length === 1 && - this.channel.chatable.users[0].status - ); - }, - - @discourseComputed("channel.chatable_type") - leaveChatTitle() { - if (this.channel.isDirectMessageChannel) { - return I18n.t("chat.direct_messages.leave"); - } else { - return I18n.t("chat.channel_settings.leave_channel"); - } - }, -}); + get #firstDirectMessageUser() { + return this.args.channel?.chatable?.users?.firstObject; + } +} diff --git a/plugins/chat/test/javascripts/acceptance/chat-keyboard-shortcuts-test.js b/plugins/chat/test/javascripts/acceptance/chat-keyboard-shortcuts-test.js index 9c18a32ffa9..9d1c144bcf4 100644 --- a/plugins/chat/test/javascripts/acceptance/chat-keyboard-shortcuts-test.js +++ b/plugins/chat/test/javascripts/acceptance/chat-keyboard-shortcuts-test.js @@ -165,7 +165,7 @@ acceptance("Discourse Chat - Keyboard shortcuts", function (needs) { test("switching channel with alt+arrow keys in float", async function (assert) { await visit("/latest"); await click(".header-dropdown-toggle.open-chat"); - await click("#chat-channel-row-4"); + await click('.chat-channel-row[data-chat-channel-id="4"]'); assert.ok(exists(`.chat-drawer.is-expanded[data-chat-channel-id="4"]`)); diff --git a/plugins/chat/test/javascripts/acceptance/chat-status-test.js b/plugins/chat/test/javascripts/acceptance/chat-status-test.js index 4ba11d24d9e..a4cd51a825d 100644 --- a/plugins/chat/test/javascripts/acceptance/chat-status-test.js +++ b/plugins/chat/test/javascripts/acceptance/chat-status-test.js @@ -71,7 +71,9 @@ acceptance( test("it clears any unread messages in the sidebar for the archived channel", async function (assert) { await visit("/chat/channel/4/public-category"); assert.ok( - exists("#chat-channel-row-4 .chat-channel-unread-indicator"), + exists( + '.chat-channel-row[data-chat-channel-id="4"] .chat-channel-unread-indicator' + ), "unread indicator shows for channel" ); @@ -80,7 +82,9 @@ acceptance( status: "archived", }); assert.notOk( - exists("#chat-channel-row-4 .chat-channel-unread-indicator"), + exists( + '.chat-channel-row[data-chat-channel-id="4"] .chat-channel-unread-indicator' + ), "unread indicator should not show after archive status change" ); }); diff --git a/plugins/chat/test/javascripts/acceptance/chat-test.js b/plugins/chat/test/javascripts/acceptance/chat-test.js index a578bed8133..a437de0c54e 100644 --- a/plugins/chat/test/javascripts/acceptance/chat-test.js +++ b/plugins/chat/test/javascripts/acceptance/chat-test.js @@ -206,7 +206,7 @@ acceptance("Discourse Chat - without unread", function (needs) { test("Unfollowing a direct message channel transitions to another channel", async function (assert) { await visit("/chat/channel/75/@hawk"); await click( - ".chat-channel-row.chat-channel-75 .toggle-channel-membership-button.-leave" + '.chat-channel-row[data-chat-channel-id="75"] .toggle-channel-membership-button.-leave' ); assert.ok(/^\/chat\/channel\/4/.test(currentURL())); @@ -353,13 +353,13 @@ acceptance("Discourse Chat - without unread", function (needs) { await settled(); await click(".chat-drawer-header__return-to-channels-btn"); - await click(".chat-channel-row.chat-channel-9"); + await click('.chat-channel-row[data-chat-channel-id="9"]'); await triggerEvent(".chat-message-container[data-id='174']", "mouseenter"); await click(".chat-message-actions-container[data-id='174'] .reply-btn"); // Reply-to line is present assert.ok(exists(".chat-composer-message-details .chat-reply")); await click(".chat-drawer-header__return-to-channels-btn"); - await click(".chat-channel-row.chat-channel-11"); + await click('.chat-channel-row[data-chat-channel-id="11"]'); // Reply-to line is gone since switching channels assert.notOk(exists(".chat-composer-message-details .chat-reply")); // Now click on reply btn and cancel it on channel 7 @@ -370,13 +370,13 @@ acceptance("Discourse Chat - without unread", function (needs) { // Go back to channel 9 and check that reply-to is present await click(".chat-drawer-header__return-to-channels-btn"); - await click(".chat-channel-row.chat-channel-9"); + await click('.chat-channel-row[data-chat-channel-id="9"]'); // Now reply-to should be back and loaded from draft assert.ok(exists(".chat-composer-message-details .chat-reply")); // Go back one for time to channel 7 and make sure reply-to is gone await click(".chat-drawer-header__return-to-channels-btn"); - await click(".chat-channel-row.chat-channel-11"); + await click('.chat-channel-row[data-chat-channel-id="11"]'); assert.notOk(exists(".chat-composer-message-details .chat-reply")); }); @@ -922,7 +922,7 @@ Widget.triangulate(arg: "test") await dropdown.expand(); await dropdown.selectRowByValue("selectMessage"); await click("#chat-copy-btn"); - await click("#chat-channel-row-9"); + await click('.chat-channel-row[data-chat-channel-id="9"]'); assert.notOk(exists("#chat-copy-btn")); }); @@ -1065,7 +1065,7 @@ acceptance( visible(".chat-drawer-header__top-line--expanded"), "drawer is expanded" ); - await click("#chat-channel-row-9"); + await click('.chat-channel-row[data-chat-channel-id="9"]'); await click(".chat-drawer-header__title"); assert.equal(currentURL(), `/chat/channel/9/site/info/members`); }); @@ -1142,7 +1142,7 @@ acceptance( test("drawer open to DM channel with unread messages with sidebar off", async function (assert) { await visit("/t/internationalization-localization/280"); await click(".header-dropdown-toggle.open-chat"); - await click("#chat-channel-row-75"); + await click('.chat-channel-row[data-chat-channel-id="75"]'); const chatContainer = query(".chat-drawer"); assert.strictEqual(chatContainer.dataset.chatChannelId, "75"); }); diff --git a/plugins/chat/test/javascripts/acceptance/mobile-chat-test.js b/plugins/chat/test/javascripts/acceptance/mobile-chat-test.js index 62db7c85bbe..e4e95fd44d5 100644 --- a/plugins/chat/test/javascripts/acceptance/mobile-chat-test.js +++ b/plugins/chat/test/javascripts/acceptance/mobile-chat-test.js @@ -39,7 +39,7 @@ acceptance("Discourse Chat - Mobile test", function (needs) { await click(".header-dropdown-toggle.open-chat"); assert.equal(currentURL(), "/chat"); assert.ok(exists(".channels-list")); - await click(".chat-channel-row.chat-channel-7"); + await click('.chat-channel-row[data-chat-channel-id="7"]'); assert.notOk(exists(".open-drawer-btn")); }); diff --git a/plugins/chat/test/javascripts/acceptance/user-card-chat-test.js b/plugins/chat/test/javascripts/acceptance/user-card-chat-test.js index 035bd8d1416..284b410944f 100644 --- a/plugins/chat/test/javascripts/acceptance/user-card-chat-test.js +++ b/plugins/chat/test/javascripts/acceptance/user-card-chat-test.js @@ -82,7 +82,7 @@ acceptance("Discourse Chat - User card test", function (needs) { test("user card has chat button that opens the correct channel", async function (assert) { await visit("/"); await click(".header-dropdown-toggle.open-chat"); - await click(".chat-channel-row.chat-channel-9"); + await click('.chat-channel-row[data-chat-channel-id="9"]'); await click("[data-user-card='hawk']"); assert.ok(exists(".user-card-chat-btn")); 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 6d3a1b37beb..4310e5eca17 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-row-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-row-test.js @@ -1,117 +1,178 @@ -import componentTest, { - setupRenderingTest, -} from "discourse/tests/helpers/component-test"; -import { exists } from "discourse/tests/helpers/qunit-helpers"; -import hbs from "htmlbars-inline-precompile"; +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 { module } from "qunit"; module("Discourse Chat | Component | chat-channel-row", function (hooks) { setupRenderingTest(hooks); - componentTest("with leaveButton", { - template: hbs`{{chat-channel-row channel=channel options=(hash leaveButton=true)}}`, + hooks.beforeEach(function () { + this.categoryChatChannel = fabricators.chatChannel(); + this.directMessageChatChannel = fabricators.directMessageChatChannel(); + }); - beforeEach() { - this.set( - "channel", - fabricators.chatChannel({ - current_user_membership: { following: true }, - }) + test("links to correct channel", async function (assert) { + await render(hbs``); + + assert + .dom(".chat-channel-row") + .hasAttribute("href", `/chat/channel/${this.categoryChatChannel.id}/-`); + }); + + test("allows tabbing", async function (assert) { + await render(hbs``); + + assert.dom(".chat-channel-row").hasAttribute("tabindex", "0"); + }); + + test("channel data attrite tabbing", async function (assert) { + await render(hbs``); + + assert + .dom(".chat-channel-row") + .hasAttribute( + "data-chat-channel-id", + this.categoryChatChannel.id.toString() ); - }, - - async test(assert) { - assert.ok(exists(".toggle-channel-membership-button.-leave")); - }, }); - componentTest("without leaveButton", { - template: hbs`{{chat-channel-row channel=channel}}`, + test("renders correct channel title", async function (assert) { + await render(hbs``); - beforeEach() { - this.set("channel", fabricators.chatChannel()); - }, - - async test(assert) { - assert.notOk(exists(".chat-channel-leave-btn")); - }, + assert.dom(".chat-channel-title").hasText(this.categoryChatChannel.title); }); - componentTest( - "a row is active when the associated channel is active and visible", - { - template: hbs`{{chat-channel-row switchChannel=switchChannel channel=channel chat=chat router=router}}`, + test("renders correct channel metadata", async function (assert) { + await render(hbs``); - beforeEach() { - this.set("channel", fabricators.chatChannel()); - this.set("chat", { activeChannel: this.channel }); - this.set("router", { currentRouteName: "chat.channel" }); - }, - - async test(assert) { - assert.ok(exists(".chat-channel-row.active")); - - this.set("router.currentRouteName", "chat.browse"); - - assert.notOk(exists(".chat-channel-row.active")); - - this.set("router.currentRouteName", "chat.channel"); - this.set("chat.activeChannel", null); - - assert.notOk(exists(".chat-channel-row.active")); - }, - } - ); - - componentTest("can receive a tab event", { - template: hbs`{{chat-channel-row channel=channel}}`, - - beforeEach() { - this.set("channel", fabricators.chatChannel()); - }, - - async test(assert) { - assert.ok(exists(".chat-channel-row[tabindex=0]")); - }, + assert + .dom(".chat-channel-metadata") + .hasText( + moment(this.categoryChatChannel.last_message_sent_at).format("l") + ); }); - componentTest("shows user status on the direct message channel", { - template: hbs`{{chat-channel-row channel=channel}}`, + test("renders membership toggling button when necessary", async function (assert) { + this.site.desktopView = false; - beforeEach() { - const status = { description: "Off to dentist", emoji: "tooth" }; - const channel = fabricators.directMessageChatChannel(); - channel.chatable.users[0].status = status; - this.set("channel", channel); - }, + await render(hbs``); - async test(assert) { - assert.ok(exists(".user-status-message")); - }, + assert.dom(".toggle-channel-membership-button").doesNotExist(); + + this.categoryChatChannel.current_user_membership.following = true; + + await render(hbs``); + + assert.dom(".toggle-channel-membership-button").doesNotExist(); + + this.site.desktopView = true; + + await render( + hbs`` + ); + + assert.dom(".toggle-channel-membership-button").exists(); }); - componentTest( - "doesn't show user status on a direct message channel with multiple users", - { - template: hbs`{{chat-channel-row channel=channel}}`, + test("focused channel has correct class", async function (assert) { + await render(hbs``); - beforeEach() { - const status = { description: "Off to dentist", emoji: "tooth" }; - const channel = fabricators.directMessageChatChannel(); - channel.chatable.users[0].status = status; - channel.chatable.users.push({ - id: 2, - username: "bill", - name: null, - avatar_template: "/letter_avatar_proxy/v3/letter/t/31188e/{size}.png", - }); - this.set("channel", channel); - }, + assert.dom(".chat-channel-row").doesNotHaveClass("focused"); - async test(assert) { - assert.notOk(exists(".user-status-message")); - }, - } - ); + this.categoryChatChannel.focused = true; + + await render(hbs``); + + assert.dom(".chat-channel-row").hasClass("focused"); + }); + + test("muted channel has correct class", async function (assert) { + await render(hbs``); + + assert.dom(".chat-channel-row").doesNotHaveClass("muted"); + + this.categoryChatChannel.current_user_membership.muted = true; + + await render(hbs``); + + assert.dom(".chat-channel-row").hasClass("muted"); + }); + + test("leaveButton options adds correct class", async function (assert) { + await render(hbs``); + + assert.dom(".chat-channel-row").doesNotHaveClass("can-leave"); + + await render( + hbs`` + ); + + assert.dom(".chat-channel-row").hasClass("can-leave"); + }); + + test("active channel adds correct class", async function (assert) { + await render(hbs``); + + assert.dom(".chat-channel-row").doesNotHaveClass("active"); + + this.owner + .lookup("service:chat") + .set("activeChannel", { id: this.categoryChatChannel.id }); + + await render(hbs``); + + assert.dom(".chat-channel-row").hasClass("active"); + }); + + test("unreads adds correct class", async function (assert) { + await render(hbs``); + + assert.dom(".chat-channel-row").doesNotHaveClass("has-unread"); + + this.owner + .lookup("service:current-user") + .set("chat_channel_tracking_state", { + [this.categoryChatChannel.id]: { unread_count: 1 }, + }); + + await render(hbs``); + + assert.dom(".chat-channel-row").hasClass("has-unread"); + }); + + test("user status with category channel", async function (assert) { + await render(hbs``); + + assert.dom(".user-status-message").doesNotExist(); + }); + + test("user status with direct message channel", async function (assert) { + const status = { description: "Off to dentist", emoji: "tooth" }; + this.directMessageChatChannel.chatable.users[0].status = status; + + await render( + hbs`` + ); + + assert.dom(".user-status-message").exists(); + }); + + 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.directMessageChatChannel.chatable.users.push({ + id: 2, + username: "bill", + name: null, + avatar_template: "/letter_avatar_proxy/v3/letter/t/31188e/{size}.png", + }); + + await render( + hbs`` + ); + + assert.dom(".user-status-message").doesNotExist(); + }); });