Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

313 lines
9.8 KiB
TypeScript

import { mockAppRoot, MockedRouterContext } from '@rocket.chat/mock-providers';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { ReactNode } from 'react';
import ThreadMetrics from './ThreadMetrics';
import ThreadMetricsFollow from './ThreadMetricsFollow';
import ThreadMetricsParticipants from './ThreadMetricsParticipants';
const toggleFollowMock =
(done: jest.DoneCallback | (() => undefined)) =>
({ mid }: { mid: string }) => {
expect(mid).toBe('mid');
done();
return null;
};
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
const mockRoot = () => {
const AppRoot = mockAppRoot();
const buildWithRouter = (navigate: (...args: any[]) => void) => {
const Wrapper = AppRoot.build();
return function Mock({ children }: { children: ReactNode }) {
return (
<Wrapper>
<MockedRouterContext router={{ navigate, getRouteName: () => 'thread' as any }}>{children}</MockedRouterContext>
</Wrapper>
);
};
};
return Object.assign(AppRoot, { buildWithRouter });
};
const mockedTranslations = [
'en',
'core',
{
Follower_one: 'follower',
Follower_other: 'followers',
__count__replies__date__: '{{count}} replies {{date}}',
__count__replies: '{{count}} replies',
},
] as const;
let inlineSize = 400;
jest.mock('@rocket.chat/fuselage-hooks', () => {
const originalModule = jest.requireActual('@rocket.chat/fuselage-hooks');
return {
...originalModule,
useResizeObserver: () => ({ ref: () => undefined, borderBoxSize: { inlineSize } }),
};
});
describe('Thread Metrics', () => {
describe('Main component', () => {
it('should render large followed with 3 participants and unread', async () => {
const navigateSpy = jest.fn();
const navigateCallback = (route: any) => {
navigateSpy(route.name, route.params.rid, route.params.tab, route.params.context);
};
render(
<ThreadMetrics
lm={new Date(2024, 6, 1, 0, 0, 0)}
counter={5}
participants={['user1', 'user2', 'user3']}
unread={true}
mention={false}
all={false}
mid='mid'
rid='rid'
following={true}
/>,
{
wrapper: mockRoot()
.withEndpoint(
'POST',
'/v1/chat.followMessage',
toggleFollowMock(() => undefined),
)
.withEndpoint(
'POST',
'/v1/chat.unfollowMessage',
toggleFollowMock(() => undefined),
)
.withUserPreference('clockMode', 1)
.withSetting('Message_TimeFormat', 'LT')
.withTranslations(...mockedTranslations)
.buildWithRouter(navigateCallback),
},
);
const followButton = screen.getByTitle('Following');
expect(followButton).toBeVisible();
const badge = screen.getByTitle('Unread');
expect(badge).toBeVisible();
expect(screen.getByTitle('followers')).toBeVisible();
expect(screen.getByText('3')).toBeVisible();
const replyButton = screen.getByText('View_thread');
expect(replyButton).toBeVisible();
await userEvent.click(replyButton);
expect(navigateSpy).toHaveBeenCalledWith('thread', 'rid', 'thread', 'mid');
const threadCount = screen.getByTitle('Last_message__date__');
expect(threadCount).toHaveTextContent('5 replies July 1st, 2024');
});
it('should render small not followed with 3 participants and unread', async () => {
const navigateSpy = jest.fn();
const navigateCallback = (route: any) => {
navigateSpy(route.name, route.params.rid, route.params.tab, route.params.context);
};
inlineSize = 200;
render(
<ThreadMetrics
lm={new Date(2024, 6, 1, 0, 0, 0)}
counter={5}
participants={['user1', 'user2', 'user3']}
unread={true}
mention={false}
all={false}
mid='mid'
rid='rid'
following={false}
/>,
{
wrapper: mockRoot()
.withEndpoint(
'POST',
'/v1/chat.followMessage',
toggleFollowMock(() => undefined),
)
.withEndpoint(
'POST',
'/v1/chat.unfollowMessage',
toggleFollowMock(() => undefined),
)
.withUserPreference('clockMode', 1)
.withSetting('Message_TimeFormat', 'LT')
.withTranslations(...mockedTranslations)
.buildWithRouter(navigateCallback),
},
);
const followButton = screen.getByTitle('Not_following');
expect(followButton).toBeVisible();
const badge = screen.getByTitle('Unread');
expect(badge).toBeVisible();
expect(screen.getByTitle('followers')).toBeVisible();
expect(screen.getByText('3')).toBeVisible();
const replyButton = screen.getByText('View_thread');
expect(replyButton).toBeVisible();
await userEvent.click(replyButton);
expect(navigateSpy).toHaveBeenCalledWith('thread', 'rid', 'thread', 'mid');
const threadCount = screen.getByTitle('Last_message__date__');
expect(threadCount).toHaveTextContent('5 replies');
});
});
describe('ThreadMetricsFollow', () => {
it('should render not followed', async () => {
render(<ThreadMetricsFollow unread={true} mention={false} all={false} mid='mid' rid='rid' following={false} />, {
wrapper: mockAppRoot()
.withEndpoint(
'POST',
'/v1/chat.followMessage',
toggleFollowMock(() => undefined),
)
.build(),
});
const followButton = screen.getByTitle('Not_following');
expect(followButton).toBeVisible();
await userEvent.click(followButton);
});
it('should render followed', async () => {
render(<ThreadMetricsFollow unread={true} mention={false} all={false} mid='mid' rid='rid' following={true} />, {
wrapper: mockAppRoot()
.withEndpoint(
'POST',
'/v1/chat.unfollowMessage',
toggleFollowMock(() => undefined),
)
.build(),
});
const followButton = screen.getByTitle('Following');
expect(followButton).toBeVisible();
await userEvent.click(followButton);
});
it('should render unread badge', () => {
render(<ThreadMetricsFollow unread={true} mention={false} all={false} mid='mid' rid='rid' following={false} />, {
wrapper: mockAppRoot().build(),
});
const badge = screen.getByTitle('Unread');
expect(badge).toBeVisible();
});
it('should render mention-all badge', () => {
render(<ThreadMetricsFollow unread={true} mention={false} all={true} mid='mid' rid='rid' following={false} />, {
wrapper: mockAppRoot().build(),
});
const badge = screen.getByTitle('mention-all');
expect(badge).toBeVisible();
});
it('should render Mentions_you badge', () => {
render(<ThreadMetricsFollow unread={true} mention={true} all={false} mid='mid' rid='rid' following={false} />, {
wrapper: mockAppRoot().build(),
});
const badge = screen.getByTitle('Mentions_you');
expect(badge).toBeVisible();
});
});
describe('ThreadMetricsParticipants', () => {
it('should render 1 avatars', () => {
render(<ThreadMetricsParticipants participants={['user1']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', true)
.withTranslations(...mockedTranslations)
.build(),
});
expect(screen.getByTitle('follower')).toBeVisible();
const avatars = screen.getAllByRole('figure');
expect(avatars.length).toBe(1);
expect(avatars.pop()).toBeVisible();
});
it('should render 2 avatars', () => {
render(<ThreadMetricsParticipants participants={['user1', 'user2']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', true)
.withTranslations(...mockedTranslations)
.build(),
});
expect(screen.getByTitle('followers')).toBeVisible();
const avatars = screen.getAllByRole('figure');
expect(avatars.length).toBe(2);
avatars.forEach((avatar) => expect(avatar).toBeVisible());
});
it('should render 2 avatars and "+1" text', () => {
render(<ThreadMetricsParticipants participants={['user1', 'user2', 'user3']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', true)
.withTranslations(...mockedTranslations)
.build(),
});
expect(screen.getByTitle('followers')).toBeVisible();
const avatars = screen.getAllByRole('figure');
expect(avatars.length).toBe(2);
avatars.forEach((avatar) => expect(avatar).toBeVisible());
expect(screen.getByText('+1')).toBeVisible();
});
it('should render 2 avatars and "+5" text', () => {
render(<ThreadMetricsParticipants participants={['user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', true)
.withTranslations(...mockedTranslations)
.build(),
});
expect(screen.getByTitle('followers')).toBeVisible();
const avatars = screen.getAllByRole('figure');
expect(avatars.length).toBe(2);
avatars.forEach((avatar) => expect(avatar).toBeVisible());
expect(screen.getByText('+5')).toBeVisible();
});
it('should render user icon and 1 follower', () => {
render(<ThreadMetricsParticipants participants={['user1']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', false)
.withTranslations(...mockedTranslations)
.build(),
});
const follower = screen.getByTitle('follower');
expect(follower).toBeVisible();
// eslint-disable-next-line testing-library/no-node-access
expect(follower.querySelector('.rcx-icon--name-user')).toBeVisible();
expect(screen.getByText('1')).toBeVisible();
});
it('should render user icon and 5 followers', () => {
render(<ThreadMetricsParticipants participants={['user1', 'user2', 'user3', 'user4', 'user5']} />, {
wrapper: mockAppRoot()
.withUserPreference('displayAvatars', false)
.withTranslations(...mockedTranslations)
.build(),
});
const follower = screen.getByTitle('followers');
expect(follower).toBeVisible();
// eslint-disable-next-line testing-library/no-node-access
expect(follower.querySelector('.rcx-icon--name-user')).toBeVisible();
expect(screen.getByText('5')).toBeVisible();
});
});
});