mattermost-mobile/app/actions/remote/entry/common.test.ts
Rahim Rahman 72b97f60f5
revert: floating banner & low connectivity (#9254)
* Revert "feat(MM-65625): low connectivity feature toggle in advance settings (#9191)"

This reverts commit c872e2e2b1.

* Revert "feat(MM-65145): Network connectivity/performance observer (#9173)"

This reverts commit 597e03d7d8.

* Revert "feat(MM-65625): floating banner (#9162)"

This reverts commit 5887c3ab46.

* Revert "feat(MM-65625): enhance navigation overlay management (#9165)"

This reverts commit f1554f0341.

* revert advanced.test.tsx changes
2025-11-04 07:15:51 -07:00

559 lines
20 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {nativeApplicationVersion} from 'expo-application';
import {RESULTS} from 'react-native-permissions';
import {handleKickFromChannel, fetchMyChannelsForTeam} from '@actions/remote/channel';
import {fetchMyPreferences} from '@actions/remote/preference';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchMyTeams, handleKickFromTeam} from '@actions/remote/team';
import {fetchMe} from '@actions/remote/user';
import {Screens} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import {PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import {getDeviceToken} from '@queries/app/global';
import {prepareEntryModels} from '@queries/servers/entry';
import {getCurrentChannelId, getCurrentTeamId, setCurrentTeamAndChannelId} from '@queries/servers/system';
import NavigationStore from '@store/navigation_store';
import {logDebug} from '@utils/log';
import {entry, setExtraSessionProps, verifyPushProxy, entryInitialChannelId, handleEntryAfterLoadNavigation} from './common';
jest.mock('@actions/remote/channel');
jest.mock('@actions/remote/scheduled_post');
jest.mock('@actions/remote/preference');
jest.mock('@actions/remote/systems');
jest.mock('@actions/remote/team');
jest.mock('@actions/remote/user');
jest.mock('@actions/remote/post');
jest.mock('@actions/remote/groups');
jest.mock('@actions/remote/thread');
jest.mock('@queries/app/global');
jest.mock('@queries/servers/system');
jest.mock('@queries/servers/entry');
jest.mock('@queries/servers/system', () => {
const original = jest.requireActual('@queries/servers/system');
return {
...original,
getCurrentTeamId: jest.fn(),
getCurrentChannelId: jest.fn(),
setCurrentTeamAndChannelId: jest.fn(original.setCurrentTeamAndChannelId),
};
});
jest.mock('react-native-permissions');
jest.mock('expo-application');
describe('actions/remote/entry/common', () => {
const serverUrl = 'https://server.example.com';
let mockClient: any;
const mockOperator = {
handleSystem: jest.fn(),
handleUsers: jest.fn(),
handlePreferences: jest.fn(),
handleTeams: jest.fn(),
handleChannels: jest.fn(),
handlePosts: jest.fn(),
handleCategories: jest.fn(),
handleGroups: jest.fn(),
handleRoles: jest.fn(),
};
const mockQuery = {
fetch: jest.fn().mockReturnValue(['true']),
};
const mockCollection = {
query: jest.fn().mockReturnValue(mockQuery),
};
const mockDatabase = {
get: jest.fn().mockReturnValue(mockCollection),
collections: {
get: jest.fn().mockReturnValue(mockCollection),
},
};
beforeEach(async () => {
jest.clearAllMocks();
mockClient = {
setExtraSessionProps: jest.fn(),
ping: jest.fn().mockResolvedValue({
data: {
CanReceiveNotifications: true,
},
}),
};
NetworkManager.init([{serverUrl} as ServerCredential]);
NetworkManager.getClient = jest.fn().mockReturnValue(mockClient);
DatabaseManager.init = jest.fn().mockResolvedValue(undefined);
DatabaseManager.getServerDatabaseAndOperator = jest.fn().mockReturnValue({
database: mockDatabase,
operator: mockOperator,
});
// Reset mock implementations
(RESULTS as any).GRANTED = 'granted';
require('react-native-permissions').checkNotifications = jest.fn().mockResolvedValue({status: RESULTS.GRANTED});
require('expo-application').nativeApplicationVersion = '0.0.0';
});
describe('entry', () => {
it('should fetch initial data successfully', async () => {
const mockConfig = {Version: '7.8.0'};
const mockLicense = {};
(fetchConfigAndLicense as jest.Mock).mockResolvedValue({error: false, config: mockConfig, license: mockLicense});
const mockPreferences = {preferences: []};
(fetchMyPreferences as jest.Mock).mockResolvedValue(mockPreferences);
const mockTeams = {teams: [], memberships: []};
(fetchMyTeams as jest.Mock).mockResolvedValue(mockTeams);
const mockUser = {user: {id: 'user1', roles: '', username: 'user1'}};
(fetchMe as jest.Mock).mockResolvedValueOnce(mockUser);
const mockChannels = {channels: [], memberships: [], categories: []};
(fetchMyChannelsForTeam as jest.Mock).mockResolvedValue(mockChannels);
(prepareEntryModels as jest.Mock).mockResolvedValue([]);
const result = await entry(serverUrl, 'team1');
expect(prepareEntryModels).toHaveBeenCalledWith({
operator: mockOperator,
teamData: mockTeams,
chData: mockChannels,
prefData: mockPreferences,
meData: mockUser,
isCRTEnabled: false,
});
expect(result).toEqual(expect.objectContaining({
initialChannelId: '',
initialTeamId: '',
models: expect.any(Array),
prefData: mockPreferences,
teamData: mockTeams,
chData: mockChannels,
meData: mockUser,
gmConverted: false,
}));
});
it('should handle errors in data fetching', async () => {
const mockError = new Error('Network error');
(fetchConfigAndLicense as jest.Mock).mockResolvedValue({error: mockError});
const result = await entry(serverUrl);
expect(result).toEqual({
error: mockError,
});
});
});
describe('entryInitialChannelId', () => {
it('should return requested channel id for DM/GM channels', async () => {
const channels = [
{id: 'dm1', type: 'D', name: 'dm-channel'},
] as Channel[];
const memberships = [{channel_id: 'dm1'}] as ChannelMembership[];
const result = await entryInitialChannelId(
mockDatabase as any,
'dm1',
'',
'',
'en',
channels,
memberships,
);
expect(result).toBe('dm1');
});
it('should return default channel when available', async () => {
const channels = [
{id: 'town-square', name: 'town-square', team_id: 'team1', type: 'O'},
] as Channel[];
const memberships = [{channel_id: 'town-square'}] as ChannelMembership[];
const result = await entryInitialChannelId(
mockDatabase as any,
'',
'team1',
'team1',
'en',
channels,
memberships,
);
expect(result).toBe('town-square');
});
});
describe('setExtraSessionProps', () => {
it('should set extra session properties when notifications are granted', async () => {
(RESULTS as any).GRANTED = 'granted';
const mockCheckNotifications = jest.fn().mockResolvedValue({status: RESULTS.GRANTED});
require('react-native-permissions').checkNotifications = mockCheckNotifications;
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
const result = await setExtraSessionProps(serverUrl);
expect(result).toEqual({});
expect(mockClient.setExtraSessionProps).toHaveBeenCalledWith(
expect.any(String),
false,
nativeApplicationVersion,
undefined,
);
});
it('should set extra session properties when notifications are limited', async () => {
(RESULTS as any).LIMITED = 'limited';
const mockCheckNotifications = jest.fn().mockResolvedValue({status: RESULTS.LIMITED});
require('react-native-permissions').checkNotifications = mockCheckNotifications;
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
const result = await setExtraSessionProps(serverUrl);
expect(result).toEqual({});
expect(mockClient.setExtraSessionProps).toHaveBeenCalledWith(
expect.any(String),
false,
nativeApplicationVersion,
undefined,
);
});
it('should handle errors gracefully', async () => {
const mockError = new Error('Failed to set props');
const mockCheckNotifications = jest.fn().mockRejectedValue(mockError);
require('react-native-permissions').checkNotifications = mockCheckNotifications;
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
const result = await setExtraSessionProps(serverUrl);
expect(result).toEqual({error: mockError});
});
});
describe('verifyPushProxy', () => {
it('should verify push proxy status successfully', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
await verifyPushProxy(serverUrl);
expect(mockClient.ping).toHaveBeenCalled();
});
it('should handle error gracefully', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
mockClient.ping.mockRejectedValueOnce(new Error('Network error'));
await verifyPushProxy(serverUrl);
expect(mockOperator.handleSystem).not.toHaveBeenCalled();
});
it('should handle verified push proxy response', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
mockClient.ping.mockResolvedValueOnce({
data: {
CanReceiveNotifications: true,
},
});
await verifyPushProxy(serverUrl);
expect(mockOperator.handleSystem).toHaveBeenCalledWith({
systems: [{
id: SYSTEM_IDENTIFIERS.PUSH_VERIFICATION_STATUS,
value: PUSH_PROXY_STATUS_VERIFIED,
}],
prepareRecordsOnly: false,
});
});
it('should handle missing device token', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('');
await verifyPushProxy(serverUrl);
expect(mockClient.ping).not.toHaveBeenCalled();
});
it('should handle push proxy not available response', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
mockClient.ping.mockResolvedValueOnce({
data: {
CanReceiveNotifications: 'false',
},
});
await verifyPushProxy(serverUrl);
expect(mockOperator.handleSystem).toHaveBeenCalledWith({
systems: [{
id: SYSTEM_IDENTIFIERS.PUSH_VERIFICATION_STATUS,
value: 'not_available',
}],
prepareRecordsOnly: false,
});
});
it('should handle push proxy unknown response', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
mockClient.ping.mockResolvedValueOnce({
data: {
CanReceiveNotifications: 'unknown',
},
});
await verifyPushProxy(serverUrl);
expect(mockOperator.handleSystem).not.toHaveBeenCalled();
});
it('should handle network error', async () => {
jest.mocked(getDeviceToken).mockResolvedValueOnce('deviceToken');
mockClient.ping.mockRejectedValueOnce(new Error('Network error'));
await verifyPushProxy(serverUrl);
expect(mockOperator.handleSystem).not.toHaveBeenCalled();
});
});
describe('handleEntryAfterLoadNavigation', () => {
const teamMembers = [{team_id: 'team1', delete_at: 0} as TeamMembership];
const channelMembers = [{channel_id: 'channel1'} as ChannelMembership];
it('should handle first load or no team scenario', async () => {
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(mockOperator.handleSystem).toHaveBeenCalled();
});
it('should handle current team switch during loading', async () => {
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team2');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(handleKickFromTeam).toHaveBeenCalled();
});
it('should handle initial team switch during loading', async () => {
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(handleKickFromTeam).toHaveBeenCalled();
});
it('should handle initial team switch during loading', async () => {
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
true,
);
expect(setCurrentTeamAndChannelId).toHaveBeenCalled();
});
it('should handle current channel switch during loading', async () => {
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
jest.mocked(getCurrentChannelId).mockResolvedValueOnce('channel2');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'team1',
'initialChannel1',
true,
);
expect(setCurrentTeamAndChannelId).toHaveBeenCalled();
});
it('should handle initial channel switch during loading', async () => {
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
jest.mocked(getCurrentChannelId).mockResolvedValueOnce('channel1');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'team1',
'initialChannel1',
true,
);
expect(setCurrentTeamAndChannelId).toHaveBeenCalled();
});
it('should handle current channel switch during loading - thread mounted', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([Screens.THREAD]);
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
jest.mocked(getCurrentChannelId).mockResolvedValueOnce('channel2');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'team1',
'initialChannel1',
true,
);
expect(handleKickFromChannel).toHaveBeenCalled();
});
it('should handle initial channel switch during loading - thread mounted', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([Screens.THREAD]);
jest.mocked(getCurrentTeamId).mockResolvedValueOnce('team1');
jest.mocked(getCurrentChannelId).mockResolvedValueOnce('channel1');
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'team1',
'initialChannel1',
true,
);
expect(handleKickFromChannel).toHaveBeenCalled();
});
it('should handle error gracefully', async () => {
(DatabaseManager.getServerDatabaseAndOperator as jest.Mock).mockImplementation(() => {
throw new Error('Test error');
});
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(logDebug).toHaveBeenCalled();
});
it('should handle tablet device scenario', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([]);
jest.spyOn(require('@utils/helpers'), 'isTablet').mockReturnValue(true);
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(mockOperator.handleSystem).toHaveBeenCalled();
});
it('should handle channel screen mounted scenario', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([Screens.CHANNEL]);
jest.spyOn(require('@utils/helpers'), 'isTablet').mockReturnValue(false);
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'differentChannel',
'team1',
'initialChannel1',
false,
);
expect(mockOperator.handleSystem).toHaveBeenCalled();
});
it('should handle threads screen mounted scenario', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([Screens.THREAD]);
jest.spyOn(require('@utils/helpers'), 'isTablet').mockReturnValue(false);
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'team1',
'differentChannel',
'team1',
'initialChannel1',
false,
);
expect(mockOperator.handleSystem).toHaveBeenCalled();
});
it('should handle kick from team scenario', async () => {
jest.spyOn(NavigationStore, 'getScreensInStack').mockReturnValue([]);
await handleEntryAfterLoadNavigation(
serverUrl,
teamMembers,
channelMembers,
'differentTeam',
'channel1',
'initialTeam1',
'initialChannel1',
false,
);
expect(mockOperator.handleSystem).toHaveBeenCalled();
});
});
});