Files
upstream-rocketchat/apps/meteor/client/hooks/useDeviceLogout.spec.tsx
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
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

174 lines
5.5 KiB
TypeScript

import { mockAppRoot } from '@rocket.chat/mock-providers';
import { QueryClient } from '@tanstack/react-query';
import { act, renderHook, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useDeviceLogout } from './useDeviceLogout';
const SESSION_ID = 'session-id';
type Endpoint = '/v1/sessions/logout' | '/v1/sessions/logout.me';
const setup = ({
isCurrentSession,
endpoint,
shouldFail = false,
openContextualBar = false,
}: {
isCurrentSession?: boolean;
endpoint: Endpoint;
shouldFail?: boolean;
openContextualBar?: boolean;
}) => {
const dispatchToastMessage = jest.fn();
const logout = jest.fn(() => Promise.resolve());
const navigate = jest.fn();
const logoutEndpoint = jest.fn(() => {
if (shouldFail) {
throw new Error('logout failed');
}
return { sessionId: SESSION_ID };
});
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
const invalidateQueries = jest.spyOn(queryClient, 'invalidateQueries');
let builder = mockAppRoot()
.withQueryClient(queryClient)
.withEndpoint('POST', endpoint, logoutEndpoint)
.withRouter({ navigate })
.withToastMessageDispatch(dispatchToastMessage)
.withLogout(logout);
// `isContextualBarOpen` is derived from the `id` route param matching the session id.
if (openContextualBar) {
builder = builder.withRouteParameter('id', SESSION_ID);
}
const { result } = renderHook(() => useDeviceLogout(SESSION_ID, endpoint, isCurrentSession), { wrapper: builder.build() });
return { result, dispatchToastMessage, logout, navigate, logoutEndpoint, invalidateQueries };
};
const openModalAndConfirm = async (openLogoutModal: () => void) => {
act(() => {
openLogoutModal();
});
const confirmButton = screen.getByRole('button', { name: 'Logout_Device' });
await userEvent.click(confirmButton);
};
describe('useDeviceLogout', () => {
it('should open a confirmation modal when the returned callback is invoked', () => {
const { result } = setup({ endpoint: '/v1/sessions/logout' });
expect(screen.queryByRole('button', { name: 'Logout_Device' })).not.toBeInTheDocument();
act(() => {
result.current();
});
expect(screen.getByRole('button', { name: 'Logout_Device' })).toBeInTheDocument();
});
describe('for another session', () => {
it('should log the device out, show a success toast and close the modal on success', async () => {
const { result, dispatchToastMessage, logout, invalidateQueries } = setup({ endpoint: '/v1/sessions/logout' });
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(dispatchToastMessage).toHaveBeenCalledWith({ type: 'success', message: 'Device_Logged_Out' });
});
expect(invalidateQueries).toHaveBeenCalled();
expect(logout).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Logout_Device' })).not.toBeInTheDocument();
});
});
it('should call the endpoint with the session id', async () => {
const { result, logoutEndpoint } = setup({ endpoint: '/v1/sessions/logout' });
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(logoutEndpoint).toHaveBeenCalledWith({ sessionId: SESSION_ID });
});
});
it('should close the contextual bar when it is open for the logged out session', async () => {
const { result, navigate } = setup({ endpoint: '/v1/sessions/logout', openContextualBar: true });
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(navigate).toHaveBeenCalledWith(expect.objectContaining({ name: 'device-management', params: {} }), expect.anything());
});
});
it('should show an error toast and close the modal on failure, without a success toast', async () => {
const { result, dispatchToastMessage, logout, invalidateQueries } = setup({
endpoint: '/v1/sessions/logout',
shouldFail: true,
});
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(dispatchToastMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' }));
});
expect(dispatchToastMessage).not.toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
expect(invalidateQueries).not.toHaveBeenCalled();
expect(logout).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Logout_Device' })).not.toBeInTheDocument();
});
});
});
describe('for the current session', () => {
it('should log the user out and close the modal on success, without any toast', async () => {
const { result, dispatchToastMessage, logout } = setup({
endpoint: '/v1/sessions/logout.me',
isCurrentSession: true,
});
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(logout).toHaveBeenCalledTimes(1);
});
expect(dispatchToastMessage).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Logout_Device' })).not.toBeInTheDocument();
});
});
it('should still log the user out and close the modal on failure, without any toast', async () => {
const { result, dispatchToastMessage, logout } = setup({
endpoint: '/v1/sessions/logout.me',
isCurrentSession: true,
shouldFail: true,
});
await openModalAndConfirm(result.current);
await waitFor(() => {
expect(logout).toHaveBeenCalledTimes(1);
});
expect(dispatchToastMessage).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Logout_Device' })).not.toBeInTheDocument();
});
});
});
});