FEATURE: display last message on mobile (#25384)

Direct messages on mobile will now display the last message in the channels list.
This commit is contained in:
Joffrey JAFFEUX 2024-01-25 15:30:21 +01:00 committed by GitHub
parent 4d1ed4a62d
commit 7b173e883f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 568 additions and 329 deletions

View File

@ -0,0 +1,49 @@
import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
import icon from "discourse-common/helpers/d-icon";
import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat-user-avatar";
export default class ChatChannelIcon extends Component {
get firstUser() {
return this.args.channel.chatable.users[0];
}
get groupDirectMessage() {
return (
this.args.channel.isDirectMessageChannel &&
this.args.channel.chatable.group
);
}
get channelColorStyle() {
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
}
<template>
{{#if @channel.isDirectMessageChannel}}
<div class="chat-channel-icon">
{{#if this.groupDirectMessage}}
<span class="chat-channel-icon --users-count">
{{@channel.membershipsCount}}
</span>
{{else}}
<div class="chat-channel-icon --avatar">
<ChatUserAvatar @user={{this.firstUser}} @interactive={{false}} />
</div>
{{/if}}
</div>
{{else if @channel.isCategoryChannel}}
<div class="chat-channel-icon">
<span
class="chat-channel-icon --category-badge"
style={{this.channelColorStyle}}
>
{{icon "d-chat"}}
{{#if @channel.chatable.read_restricted}}
{{icon "lock" class="chat-channel-icon__restricted-category-icon"}}
{{/if}}
</span>
</div>
{{/if}}
</template>
}

View File

@ -0,0 +1,79 @@
import Component from "@glimmer/component";
import { get, hash } from "@ember/helper";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import PluginOutlet from "discourse/components/plugin-outlet";
import UserStatusMessage from "discourse/components/user-status-message";
import replaceEmoji from "discourse/helpers/replace-emoji";
export default class ChatChannelName extends Component {
@service currentUser;
get firstUser() {
return this.args.channel.chatable.users[0];
}
get users() {
return this.args.channel.chatable.users;
}
get groupDirectMessage() {
return (
this.args.channel.isDirectMessageChannel &&
this.args.channel.chatable.group
);
}
get groupsDirectMessageTitle() {
return this.args.channel.title || this.usernames;
}
get usernames() {
return this.users.mapBy("username").join(", ");
}
get channelColorStyle() {
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
}
get showUserStatus() {
return !!(this.users.length === 1 && this.users[0].status);
}
<template>
<div class="chat-channel-name">
{{#if @channel.isDirectMessageChannel}}
{{#if this.groupDirectMessage}}
<span class="chat-channel-name__label">
{{this.groupsDirectMessageTitle}}
</span>
{{else}}
<span class="chat-channel-name__label">
{{this.firstUser.username}}
</span>
{{#if this.showUserStatus}}
<UserStatusMessage
@status={{get this.users "0.status"}}
@showDescription={{if this.site.mobileView "true"}}
class="chat-channel__user-status-message"
/>
{{/if}}
<PluginOutlet
@name="after-chat-channel-username"
@outletArgs={{hash user=@user}}
@tagName=""
@connectorTagName=""
/>
{{/if}}
{{else if @channel.isCategoryChannel}}
<span class="chat-channel-name__label">
{{replaceEmoji @channel.title}}
</span>
{{#if (has-block)}}
{{yield}}
{{/if}}
{{/if}}
</div>
</template>
}

View File

@ -1,111 +1,21 @@
import Component from "@glimmer/component";
import { get, hash } from "@ember/helper";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import PluginOutlet from "discourse/components/plugin-outlet";
import UserStatusMessage from "discourse/components/user-status-message";
import replaceEmoji from "discourse/helpers/replace-emoji";
import icon from "discourse-common/helpers/d-icon";
import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat-user-avatar";
import concatClass from "discourse/helpers/concat-class";
import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-icon";
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
export default class ChatChannelTitle extends Component {
@service currentUser;
const ChatChannelTitle = <template>
<div
class={{concatClass
"chat-channel-title"
(if @channel.isDirectMessageChannel "is-dm" "is-category")
}}
>
<ChannelIcon @channel={{@channel}} />
<ChannelName @channel={{@channel}} />
get firstUser() {
return this.args.channel.chatable.users[0];
}
get users() {
return this.args.channel.chatable.users;
}
get groupDirectMessage() {
return (
this.args.channel.isDirectMessageChannel &&
this.args.channel.chatable.group
);
}
get groupsDirectMessageTitle() {
return this.args.channel.title || this.usernames;
}
get usernames() {
return this.users.mapBy("username").join(", ");
}
get channelColorStyle() {
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
}
get showUserStatus() {
return !!(this.users.length === 1 && this.users[0].status);
}
<template>
{{#if @channel.isDirectMessageChannel}}
<div class="chat-channel-title is-dm">
{{#if this.groupDirectMessage}}
<span class="chat-channel-title__users-count">
{{@channel.membershipsCount}}
</span>
{{else}}
<div class="chat-channel-title__avatar">
<ChatUserAvatar @user={{this.firstUser}} @interactive={{false}} />
</div>
{{/if}}
<div class="chat-channel-title__user-info">
<div class="chat-channel-title__usernames">
{{#if this.groupDirectMessage}}
<span class="chat-channel-title__name">
{{this.groupsDirectMessageTitle}}
</span>
{{else}}
<span class="chat-channel-title__name">
{{this.firstUser.username}}
</span>
{{#if this.showUserStatus}}
<UserStatusMessage
@status={{get this.users "0.status"}}
@showDescription={{if this.site.mobileView "true"}}
class="chat-channel-title__user-status-message"
/>
{{/if}}
<PluginOutlet
@name="after-chat-channel-username"
@outletArgs={{hash user=@user}}
@tagName=""
@connectorTagName=""
/>
{{/if}}
</div>
</div>
{{#if (has-block)}}
{{yield}}
{{/if}}
</div>
{{else if @channel.isCategoryChannel}}
<div class="chat-channel-title is-category">
<span
class="chat-channel-title__category-badge"
style={{this.channelColorStyle}}
>
{{icon "d-chat"}}
{{#if @channel.chatable.read_restricted}}
{{icon "lock" class="chat-channel-title__restricted-category-icon"}}
{{/if}}
</span>
<span class="chat-channel-title__name">
{{replaceEmoji @channel.title}}
</span>
{{#if (has-block)}}
{{yield}}
{{/if}}
</div>
{{#if (has-block)}}
{{yield}}
{{/if}}
</template>
}
</div>
</template>;
export default ChatChannelTitle;

View File

@ -18,9 +18,9 @@ export default class ChatChannelMetadata extends Component {
}
<template>
<div class="chat-channel-metadata">
<div class="chat-channel__metadata">
{{#if @channel.lastMessage}}
<div class="chat-channel-metadata__date">
<div class="chat-channel__metadata-date">
{{this.lastMessageFormattedDate}}
</div>
{{/if}}

View File

@ -9,13 +9,15 @@ import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { modifier } from "ember-modifier";
import concatClass from "discourse/helpers/concat-class";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { popupAjaxError } from "discourse/lib/ajax-error";
import icon from "discourse-common/helpers/d-icon";
import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
import and from "truth-helpers/helpers/and";
import eq from "truth-helpers/helpers/eq";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-icon";
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
import ChatChannelMetadata from "discourse/plugins/chat/discourse/components/chat-channel-metadata";
import ToggleChannelMembershipButton from "discourse/plugins/chat/discourse/components/toggle-channel-membership-button";
@ -142,6 +144,14 @@ export default class ChatChannelRow extends Component {
return this.args.channel.tracking.unreadCount > 0;
}
get shouldRenderLastMessage() {
return (
this.site.mobileView &&
this.args.channel.isDirectMessageChannel &&
this.args.channel.lastMessage
);
}
get #firstDirectMessageUser() {
return this.args.channel?.chatable?.users?.firstObject;
}
@ -177,6 +187,7 @@ export default class ChatChannelRow extends Component {
<div
class={{concatClass
"chat-channel-row__content"
(if @channel.isCategoryChannel "is-category" "is-dm")
(if this.shouldReset "-animate-reset")
}}
{{(if this.shouldHandleSwipe (modifier this.registerSwipableRow))}}
@ -184,8 +195,19 @@ export default class ChatChannelRow extends Component {
{{(if this.shouldReset (modifier this.onReset))}}
style={{this.rowStyle}}
>
<ChannelTitle @channel={{@channel}} />
<ChatChannelMetadata @channel={{@channel}} @unreadIndicator={{true}} />
<ChannelIcon @channel={{@channel}} />
<div class="chat-channel-row__info">
<ChannelName @channel={{@channel}} />
<ChatChannelMetadata
@channel={{@channel}}
@unreadIndicator={{true}}
/>
{{#if this.shouldRenderLastMessage}}
<div class="chat-channel__last-message">
{{replaceEmoji (htmlSafe @channel.lastMessage.excerpt)}}
</div>
{{/if}}
</div>
{{#if
(and @options.leaveButton @channel.isFollowing this.site.desktopView)

View File

@ -0,0 +1,56 @@
.chat-channel-icon {
flex-shrink: 0;
&.--users-count {
display: flex;
justify-content: center;
align-items: center;
width: 22px;
height: 22px;
border-radius: 50%;
background: rgba(var(--primary-rgb), 0.1);
box-sizing: border-box;
color: var(--primary-high);
padding: 0;
.chat-channel-row & {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
font-size: var(--font-up-1);
}
}
&.--avatar {
.chat-user-avatar {
.avatar {
.chat-channel-row & {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
}
}
}
}
&.--category-badge {
color: var(--primary-medium);
display: flex;
font-size: var(--font-up-1);
position: relative;
.chat-message-creator & {
justify-content: center;
width: 24px;
}
}
&__restricted-category-icon {
background-color: var(--secondary);
position: absolute;
border-radius: 50%;
padding: 2px 2px 3px;
color: var(--primary-high);
height: 0.5rem;
width: 0.5rem;
right: -0.5rem;
top: -0.1rem;
}
}

View File

@ -0,0 +1,18 @@
.chat-channel-name {
@include ellipsis;
color: var(--primary);
.has-unread & {
font-weight: bold;
}
&__label {
white-space: nowrap;
}
.emoji {
height: 1.2em;
vertical-align: text-bottom;
width: 1.2em;
}
}

View File

@ -5,15 +5,7 @@
justify-content: space-between;
position: relative;
cursor: pointer;
color: var(--primary-high);
&__content {
display: flex;
justify-content: space-between;
max-width: 100%;
align-items: center;
flex-grow: 1;
}
color: var(--primary);
@media (hover: none) {
&:hover,
@ -41,7 +33,7 @@
}
}
.chat-channel-metadata {
.chat-channel__metadata {
display: none;
}
}
@ -49,55 +41,79 @@
&:hover,
&.active {
.chat-channel-title {
&,
.category-chat-name,
.dm-usernames {
color: var(--primary);
}
.chat-channel-name {
color: var(--primary);
}
.d-icon-lock {
background-color: var(--primary-low);
}
.d-icon-lock {
background-color: var(--primary-low);
}
}
&:visited {
color: var(--primary-high);
color: var(--primary);
}
&.muted {
opacity: 0.65;
}
.chat-channel-title {
&__content {
display: flex;
max-width: 100%;
align-items: center;
flex-grow: 1;
gap: 0.75rem;
overflow: hidden;
}
&__users-count {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
padding: 0;
font-size: var(--font-up-1);
justify-content: center;
flex-shrink: 0;
&__info {
display: flex;
justify-content: space-between;
align-items: center;
flex-grow: 1;
overflow: hidden;
}
.chat-channel {
&__user-info,
&__channel-info {
white-space: nowrap;
overflow: hidden;
@include ellipsis;
display: flex;
align-items: center;
}
&__avatar {
.chat-user-avatar {
img {
width: calc(var(--channel-list-avatar-size) - 2px);
height: calc(var(--channel-list-avatar-size) - 2px);
&__metadata {
display: flex;
align-items: flex-end;
flex-direction: column;
margin-left: 0.5em;
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}
&__user-info {
@include ellipsis;
}
&__usernames {
display: flex;
align-items: center;
justify-content: start;
&__metadata-date {
color: var(--primary-high);
font-size: var(--font-down-2);
white-space: nowrap;
}
.user-status-message {
display: inline-block;
font-size: var(--font-down-2);
@ -109,35 +125,6 @@
}
}
.chat-channel-metadata {
display: flex;
align-items: flex-end;
flex-direction: column;
margin-left: 0.5em;
&__date {
color: var(--primary-high);
font-size: var(--font-down-2);
white-space: nowrap;
}
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}
&.unfollowing {
opacity: 0;
}
@ -146,8 +133,4 @@
display: none;
margin-left: auto;
}
.emoji {
margin-left: 0.3em;
}
}

View File

@ -1,7 +1,8 @@
.chat-channel-title {
@include ellipsis;
display: flex;
align-items: center;
@include ellipsis;
gap: 0.5rem;
.chat-channel-preview-card & {
max-width: 100%;
@ -20,21 +21,6 @@
overflow: hidden;
}
.chat-name,
.category-chat-name,
&__usernames,
.dm-usernames {
@include ellipsis;
font-size: var(--font-0);
margin: 0;
.emoji {
height: 1.2em;
vertical-align: text-bottom;
width: 1.2em;
}
}
.d-icon-lock {
margin-right: 0.25em;
}
@ -109,20 +95,3 @@
width: 1.2em;
}
}
.chat-channel-title__name {
@include ellipsis;
font-size: var(--font-0);
color: var(--primary);
margin-left: 0.5rem;
}
.channel-info {
.chat-channel-title__name {
max-width: 100%;
}
}
.has-unread .chat-channel-title__name {
font-weight: bold;
}

View File

@ -5,7 +5,9 @@
@import "chat-browse";
@import "chat-channel";
@import "chat-channel-card";
@import "chat-channel-icon";
@import "chat-channel-info";
@import "chat-channel-name";
@import "chat-channel-preview-card";
@import "chat-channel-title";
@import "chat-composer-dropdown";

View File

@ -1,3 +1,6 @@
:root {
--channel-list-avatar-size: 43px;
}
.chat-channel-row {
margin: 0;
border-radius: 0;
@ -5,8 +8,8 @@
transition: height 0.25s ease-in-out, opacity 0.25s ease-out;
transform-origin: top center;
will-change: height, opacity, left;
height: 4em;
position: relative;
height: 4em;
&.-fade-out {
.chat-channel-row__content {
@ -20,11 +23,13 @@
&__content {
padding-inline: 1.5rem;
padding-block: 0.5rem;
z-index: 2;
background: var(--primary-very-low);
box-sizing: border-box;
height: 100%;
transition: border-radius 0.25s ease-in-out;
//for sliding delete animation
background: var(--primary-very-low);
height: 100%;
&.-animate-reset {
transition: margin-right 0.15s ease-out;
@ -36,6 +41,28 @@
}
}
&__info {
.is-category & {
display: flex;
justify-content: space-between;
align-items: center;
flex-grow: 1;
}
.is-dm & {
display: grid;
grid-template-areas:
"name metadata"
"msg msg";
width: 100%;
align-items: center;
&:has(.chat-channel-unread-indicator) {
grid-template-areas:
"name metadata"
"msg metadata";
}
}
}
&__action-btn {
z-index: 1;
display: flex;
@ -69,13 +96,32 @@
}
}
.chat-channel-metadata {
.chat-channel-unread-indicator {
font-size: var(--font-down-2);
margin-top: 0.25rem;
.chat-channel {
&__user-info,
&__channel-info {
grid-area: name;
}
&__date {
&__metadata {
grid-area: metadata;
.chat-channel-unread-indicator {
font-size: var(--font-down-2);
margin-top: 0.25rem;
}
}
&__metadata-date {
font-size: var(--font-down-2);
}
&__last-message {
@include ellipsis;
grid-area: msg;
font-size: var(--font-down-1-rem);
color: var(--primary-high);
}
}
.chat-channel-icon {
&.--users-count {
font-size: var(--font-up-2);
}
}
}

View File

@ -101,7 +101,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
expect(page.find(".c-channel-settings__name")["innerHTML"].strip).to eq(
"&lt;script&gt;alert('hello')&lt;/script&gt;",
)
expect(page.find(".chat-channel-title__name")["innerHTML"].strip).to eq(
expect(page.find(".chat-channel-name__label")["innerHTML"].strip).to eq(
"&lt;script&gt;alert('hello')&lt;/script&gt;",
)
end

View File

@ -15,6 +15,20 @@ RSpec.describe "List channels | mobile", type: :system, mobile: true do
context "when category channels" do
fab!(:category_channel_1) { Fabricate(:category_channel) }
it "doesnt show the last message" do
message =
Fabricate(
:chat_message,
chat_channel: category_channel_1,
user: current_user,
use_service: true,
)
visit("/chat/direct-messages")
expect(page).to have_no_selector(".chat-channel__last-message", text: message.message)
end
context "when member of the channel" do
before { category_channel_1.add(current_user) }
@ -61,6 +75,20 @@ RSpec.describe "List channels | mobile", type: :system, mobile: true do
fab!(:dm_channel_1) { Fabricate(:direct_message_channel, users: [current_user]) }
fab!(:inaccessible_dm_channel_1) { Fabricate(:direct_message_channel) }
it "show the last message" do
message =
Fabricate(
:chat_message,
chat_channel: dm_channel_1,
user: current_user,
use_service: true,
)
visit("/chat/direct-messages")
expect(page).to have_selector(".chat-channel__last-message", text: message.message)
end
context "when member of the channel" do
it "shows the channel in the correct section" do
visit("/chat/direct-messages")
@ -80,6 +108,7 @@ RSpec.describe "List channels | mobile", type: :system, mobile: true do
context "when no category channels" do
it "hides the section" do
visit("/chat/channels")
expect(page).to have_no_css(".channels-list-container")
end

View File

@ -0,0 +1,81 @@
import { render } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-icon";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel";
module("Discourse Chat | Component | <ChannelIcon />", function (hooks) {
setupRenderingTest(hooks);
test("category channel - badge", async function (assert) {
const channel = fabricators.channel();
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.strictEqual(
query(".chat-channel-icon.--category-badge").getAttribute("style"),
`color: #${channel.chatable.color}`
);
});
test("category channel - escapes label", async function (assert) {
const channel = fabricators.channel({
chatable_type: CHATABLE_TYPES.categoryChannel,
title: "<div class='xss'>evil</div>",
});
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.false(exists(".xss"));
});
test("category channel - read restricted", async function (assert) {
const channel = fabricators.channel({
chatable: fabricators.category({ read_restricted: true }),
});
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.true(exists(".d-icon-lock"));
});
test("category channel - not read restricted", async function (assert) {
const channel = fabricators.channel({
chatable: fabricators.category({ read_restricted: false }),
});
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.false(exists(".d-icon-lock"));
});
test("dm channel - one user", async function (assert) {
const channel = fabricators.directMessageChannel({
chatable: fabricators.directMessage({
users: [fabricators.user()],
}),
});
const user = channel.chatable.users[0];
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.true(exists(`.chat-user-avatar .avatar[title="${user.username}"]`));
});
test("dm channel - multiple users", async function (assert) {
const channel = fabricators.directMessageChannel({
users: [fabricators.user(), fabricators.user(), fabricators.user()],
});
channel.chatable.group = true;
const users = channel.chatable.users;
await render(<template><ChannelIcon @channel={{channel}} /></template>);
assert.strictEqual(
parseInt(query(".chat-channel-icon.--users-count").innerText.trim(), 10),
users.length
);
});
});

View File

@ -0,0 +1,63 @@
import { render } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel";
const CHANNEL_NAME_LABEL = ".chat-channel-name__label";
module("Discourse Chat | Component | <ChannelName />", function (hooks) {
setupRenderingTest(hooks);
test("category channel - label", async function (assert) {
const channel = fabricators.channel();
await render(<template><ChannelName @channel={{channel}} /></template>);
assert.strictEqual(query(CHANNEL_NAME_LABEL).innerText, channel.title);
});
test("category channel - escapes label", async function (assert) {
const channel = fabricators.channel({
chatable_type: CHATABLE_TYPES.categoryChannel,
title: "<div class='xss'>evil</div>",
});
await render(<template><ChannelName @channel={{channel}} /></template>);
assert.false(exists(".xss"));
});
test("dm channel - one user", async function (assert) {
const channel = fabricators.directMessageChannel({
chatable: fabricators.directMessage({
users: [fabricators.user()],
}),
});
const user = channel.chatable.users[0];
await render(<template><ChannelName @channel={{channel}} /></template>);
assert.strictEqual(
query(CHANNEL_NAME_LABEL).innerText.trim(),
user.username
);
});
test("dm channel - multiple users", async function (assert) {
const channel = fabricators.directMessageChannel({
users: [fabricators.user(), fabricators.user(), fabricators.user()],
});
channel.chatable.group = true;
const users = channel.chatable.users;
await render(<template><ChannelName @channel={{channel}} /></template>);
assert.strictEqual(
query(CHANNEL_NAME_LABEL).innerText.trim(),
users.mapBy("username").join(", ")
);
});
});

View File

@ -0,0 +1,25 @@
import { render } from "@ember/test-helpers";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
module("Discourse Chat | Component | <ChannelTitle />", function (hooks) {
setupRenderingTest(hooks);
test("icon", async function (assert) {
const channel = fabricators.channel();
await render(<template><ChannelTitle @channel={{channel}} /></template>);
assert.dom(".chat-channel-icon").exists();
});
test("label", async function (assert) {
const channel = fabricators.channel();
await render(<template><ChannelTitle @channel={{channel}} /></template>);
assert.dom(".chat-channel-name").exists();
});
});

View File

@ -1,95 +0,0 @@
import { render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel";
module("Discourse Chat | Component | <ChannelTitle />", function (hooks) {
setupRenderingTest(hooks);
test("category channel", async function (assert) {
this.channel = fabricators.channel();
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
assert.strictEqual(
query(".chat-channel-title__category-badge").getAttribute("style"),
`color: #${this.channel.chatable.color}`
);
assert.strictEqual(
query(".chat-channel-title__name").innerText,
this.channel.title
);
});
test("category channel - escapes title", async function (assert) {
this.channel = fabricators.channel({
chatable_type: CHATABLE_TYPES.categoryChannel,
title: "<div class='xss'>evil</div>",
});
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
assert.false(exists(".xss"));
});
test("category channel - read restricted", async function (assert) {
this.channel = fabricators.channel({
chatable: fabricators.category({ read_restricted: true }),
});
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
assert.true(exists(".d-icon-lock"));
});
test("category channel - not read restricted", async function (assert) {
this.channel = fabricators.channel({
chatable: fabricators.category({ read_restricted: false }),
});
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
assert.false(exists(".d-icon-lock"));
});
test("direct message channel - one user", async function (assert) {
this.channel = fabricators.directMessageChannel({
chatable: fabricators.directMessage({
users: [fabricators.user()],
}),
});
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
const user = this.channel.chatable.users[0];
assert.true(exists(`.chat-user-avatar .avatar[title="${user.username}"]`));
assert.strictEqual(
query(".chat-channel-title__name").innerText.trim(),
user.username
);
});
test("direct message channel - multiple users", async function (assert) {
this.channel = fabricators.directMessageChannel({
users: [fabricators.user(), fabricators.user(), fabricators.user()],
});
this.channel.chatable.group = true;
await render(hbs`<ChannelTitle @channel={{this.channel}} />`);
const users = this.channel.chatable.users;
assert.strictEqual(
parseInt(query(".chat-channel-title__users-count").innerText.trim(), 10),
users.length
);
assert.strictEqual(
query(".chat-channel-title__name").innerText.trim(),
users.mapBy("username").join(", ")
);
});
});

View File

@ -18,14 +18,14 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
assert.dom(".chat-channel-metadata__date").hasText("Yesterday");
assert.dom(".chat-channel__metadata-date").hasText("Yesterday");
lastMessageSentAt = moment();
this.channel.lastMessage.createdAt = lastMessageSentAt;
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
assert
.dom(".chat-channel-metadata__date")
.dom(".chat-channel__metadata-date")
.hasText(lastMessageSentAt.format("LT"));
});

View File

@ -24,13 +24,13 @@ module(
await render(hbs`<ChatChannelPreviewCard @channel={{this.channel}} />`);
assert.strictEqual(
query(".chat-channel-title__name").innerText,
query(".chat-channel-name__label").innerText,
this.channel.title,
"it shows the channel title"
);
assert.true(
exists(query(".chat-channel-title__category-badge")),
exists(query(".chat-channel-icon.--category-badge")),
"it shows the category hashtag badge"
);
});

View File

@ -43,7 +43,9 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) {
test("renders correct channel title", async function (assert) {
await render(hbs`<ChatChannelRow @channel={{this.categoryChatChannel}} />`);
assert.dom(".chat-channel-title").hasText(this.categoryChatChannel.title);
assert
.dom(".chat-channel-name__label")
.hasText(this.categoryChatChannel.title);
});
test("renders correct channel metadata", async function (assert) {
@ -53,7 +55,7 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) {
await render(hbs`<ChatChannelRow @channel={{this.categoryChatChannel}} />`);
assert
.dom(".chat-channel-metadata")
.dom(".chat-channel__metadata-date")
.hasText(
moment(this.categoryChatChannel.lastMessage.createdAt).format("h:mm A")
);