mattermost-mobile/app/products/calls/hooks.test.ts
Daniel Espino García af0a7525c8
Refactor Open Profile (#8746)
* Refactor Open Profile

* Add missing changes

* Fix tests

* Fix tests

* Address feedback
2025-05-13 17:07:13 +02:00

280 lines
9.1 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {act, renderHook} from '@testing-library/react-hooks';
import {Alert, AppState} from 'react-native';
import Permissions from 'react-native-permissions';
import {initializeVoiceTrack} from '@calls/actions/calls';
import {
getCurrentCall,
getCallsConfig,
setMicPermissionsGranted,
useCallsState,
useChannelsWithCalls,
useCurrentCall,
useGlobalCallsState,
useIncomingCalls,
} from '@calls/state';
import {Screens} from '@constants';
import {
CURRENT_CALL_BAR_HEIGHT,
JOIN_CALL_BAR_HEIGHT,
} from '@constants/view';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import {queryAllActiveServers} from '@queries/app/servers';
import {getCurrentUser} from '@queries/servers/user';
import {openUserProfileModal, openAsBottomSheet} from '@screens/navigation';
import {useTryCallsFunction, usePermissionsChecker, useCallsAdjustment, useHostControlsAvailable, useHostMenus} from './hooks';
jest.mock('react-intl', () => ({
useIntl: jest.fn().mockReturnValue({
formatMessage: jest.fn(({defaultMessage}) => defaultMessage),
}),
defineMessage: (message: any) => message,
defineMessages: (messages: any) => messages,
}));
jest.mock('@context/server', () => ({
useServerUrl: jest.fn(() => 'server1'),
}));
jest.mock('@context/theme', () => ({
useTheme: jest.fn(() => ({})),
}));
jest.mock('@calls/actions/calls', () => ({
initializeVoiceTrack: jest.fn(),
}));
jest.mock('@calls/state', () => ({
getCurrentCall: jest.fn(),
getCallsConfig: jest.fn(),
setMicPermissionsGranted: jest.fn(),
useCallsState: jest.fn(),
useChannelsWithCalls: jest.fn(),
useCurrentCall: jest.fn(),
useGlobalCallsState: jest.fn(),
useIncomingCalls: jest.fn(),
}));
jest.mock('@managers/network_manager', () => ({
getClient: jest.fn(),
}));
jest.mock('@queries/app/servers', () => ({
queryAllActiveServers: jest.fn(),
}));
jest.mock('@queries/servers/user', () => ({
getCurrentUser: jest.fn(),
}));
jest.mock('@screens/navigation', () => ({
openAsBottomSheet: jest.fn(),
openUserProfileModal: jest.fn(),
}));
describe('Calls Hooks', () => {
beforeAll(async () => {
await DatabaseManager.init(['server1']);
});
beforeEach(() => {
jest.clearAllMocks();
});
describe('useTryCallsFunction', () => {
const mockFn = jest.fn();
const mockClient = {
getEnabled: jest.fn(),
};
beforeEach(() => {
(NetworkManager.getClient as jest.Mock).mockReturnValue(mockClient);
});
it('executes function when calls enabled', async () => {
mockClient.getEnabled.mockResolvedValue(true);
const {result} = renderHook(() => useTryCallsFunction(mockFn));
const [tryFn] = result.current;
await act(async () => {
await tryFn();
});
expect(mockFn).toHaveBeenCalled();
});
it('shows alert when calls not enabled', async () => {
mockClient.getEnabled.mockResolvedValue(false);
const mockAlert = jest.spyOn(Alert, 'alert');
const {result} = renderHook(() => useTryCallsFunction(mockFn));
const [tryFn] = result.current;
await act(async () => {
await tryFn();
});
expect(mockFn).not.toHaveBeenCalled();
expect(mockAlert).toHaveBeenCalled();
});
});
describe('usePermissionsChecker', () => {
beforeEach(() => {
jest.spyOn(AppState, 'addEventListener');
});
it('checks permissions when not granted', async () => {
const mockCheck = jest.spyOn(Permissions, 'check').mockResolvedValue(Permissions.RESULTS.GRANTED);
const {result} = renderHook(() => usePermissionsChecker(false));
expect(result.current).toBe(false);
// Simulate app becoming active
await act(async () => {
AppState.currentState = 'active';
const callback = (AppState.addEventListener as jest.Mock).mock.calls[0][1];
await callback('active');
});
expect(mockCheck).toHaveBeenCalled();
expect(initializeVoiceTrack).toHaveBeenCalled();
expect(setMicPermissionsGranted).toHaveBeenCalledWith(true);
expect(result.current).toBe(true);
});
it('skips check when already granted', () => {
const mockCheck = jest.spyOn(Permissions, 'check');
renderHook(() => usePermissionsChecker(true));
expect(mockCheck).not.toHaveBeenCalled();
});
});
describe('useCallsAdjustment', () => {
beforeEach(() => {
(useIncomingCalls as jest.Mock).mockReturnValue({incomingCalls: []});
(useChannelsWithCalls as jest.Mock).mockReturnValue({});
(useCallsState as jest.Mock).mockReturnValue({calls: {}, myUserId: 'user1'});
(useGlobalCallsState as jest.Mock).mockReturnValue({micPermissionsGranted: true});
(useCurrentCall as jest.Mock).mockReturnValue(null);
(queryAllActiveServers as jest.Mock).mockReturnValue({
fetch: () => Promise.resolve([{id: 'server1'}]),
});
});
it('calculates adjustment with no calls', () => {
const {result} = renderHook(() => useCallsAdjustment('server1', 'channel1'));
expect(result.current).toBe(0);
});
it('includes current call bar height', () => {
(useCurrentCall as jest.Mock).mockReturnValue({id: 'call1', channelId: 'channel1'});
const {result} = renderHook(() => useCallsAdjustment('server1', 'channel1'));
expect(result.current).toBe(CURRENT_CALL_BAR_HEIGHT + 8);
});
it('includes join call banner height', () => {
(useChannelsWithCalls as jest.Mock).mockReturnValue({channel1: true});
const {result} = renderHook(() => useCallsAdjustment('server1', 'channel1'));
expect(result.current).toBe(JOIN_CALL_BAR_HEIGHT + 8);
});
});
describe('useHostControlsAvailable', () => {
beforeEach(() => {
(getCallsConfig as jest.Mock).mockReturnValue({
HostControlsAllowed: true,
});
(getCurrentUser as jest.Mock).mockResolvedValue({
roles: 'system_user',
});
});
it('returns true for host', async () => {
(getCurrentCall as jest.Mock).mockReturnValueOnce({
serverUrl: 'server1',
myUserId: 'user1',
hostId: 'user1',
});
const {result} = renderHook(() => useHostControlsAvailable());
expect(result.current).toBe(true);
});
it('returns true for admin', async () => {
(getCurrentCall as jest.Mock).mockReturnValueOnce({
serverUrl: 'server1',
myUserId: 'user1',
hostId: 'host1',
});
(getCurrentUser as jest.Mock).mockResolvedValueOnce({
roles: 'system_admin',
});
const {result, waitForNextUpdate} = renderHook(() => useHostControlsAvailable());
await waitForNextUpdate();
expect(result.current).toBe(true);
});
});
describe('useHostMenus', () => {
const mockSession = {
userId: 'user1',
sessionId: 'session1',
muted: false,
raisedHand: 0,
};
beforeEach(() => {
(useCurrentCall as jest.Mock).mockReturnValue({
myUserId: 'user1',
hostId: 'host1',
});
(getCallsConfig as jest.Mock).mockReturnValue({
HostControlsAllowed: true,
});
});
it('opens host controls when admin', async () => {
(getCurrentUser as jest.Mock).mockResolvedValueOnce({
roles: 'system_admin',
});
const {result} = renderHook(() => useHostMenus());
await act(async () => {
await result.current.onPress(mockSession)();
});
expect(openAsBottomSheet).toHaveBeenCalledWith(expect.objectContaining({
screen: Screens.CALL_HOST_CONTROLS,
}));
});
it('opens host controls when host and clicking another profile', async () => {
(useCurrentCall as jest.Mock).mockReturnValue({
myUserId: 'user1',
hostId: 'user1',
});
const {result} = renderHook(() => useHostMenus());
await act(async () => {
await result.current.onPress(mockSession)();
});
expect(openUserProfileModal).toHaveBeenCalledWith(
expect.any(Object),
expect.any(Object),
expect.objectContaining({
userId: mockSession.userId,
}),
);
});
});
});