Add model mocks to TestHelper (#8715)

* Add model mocks to TestHelper

* Address feedback
This commit is contained in:
Daniel Espino García 2025-03-28 09:40:08 +01:00 committed by GitHub
parent 97210af090
commit 85e01a7fb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 1611 additions and 1059 deletions

View file

@ -21,7 +21,6 @@ import {
} from './post';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type UserModel from '@typings/database/models/servers/user';
const serverUrl = 'baseHandler.test.com';
let operator: ServerDataOperator;
@ -47,11 +46,11 @@ jest.mock('@queries/servers/thread', () => {
});
const channelId = 'channelid1';
const user: UserProfile = {
const user: UserProfile = TestHelper.fakeUser({
id: 'userid',
username: 'username',
roles: '',
} as UserProfile;
});
beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
@ -64,7 +63,7 @@ afterEach(async () => {
describe('sendAddToChannelEphemeralPost', () => {
it('handle not found database', async () => {
const {posts, error} = await sendAddToChannelEphemeralPost('foo', {} as UserModel, ['username2'], ['added username2'], channelId, '');
const {posts, error} = await sendAddToChannelEphemeralPost('foo', TestHelper.fakeUserModel(), ['username2'], ['added username2'], channelId, '');
expect(posts).toBeUndefined();
expect(error).toBeTruthy();
});
@ -139,7 +138,7 @@ describe('removePost', () => {
});
it('base case - system message', async () => {
const systemPost = TestHelper.fakePost({channel_id: channelId, id: `${COMBINED_USER_ACTIVITY}id1_id2`, type: Post.POST_TYPES.COMBINED_USER_ACTIVITY as PostType, props: {system_post_ids: ['id1']}});
const systemPost = TestHelper.fakePost({channel_id: channelId, id: `${COMBINED_USER_ACTIVITY}id1_id2`, type: Post.POST_TYPES.COMBINED_USER_ACTIVITY, props: {system_post_ids: ['id1']}});
await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL,
@ -187,16 +186,16 @@ describe('markPostAsDeleted', () => {
describe('storePostsForChannel', () => {
const post = TestHelper.fakePost({channel_id: channelId, user_id: user.id});
const teamId = 'tId1';
const channel: Channel = {
const channel: Channel = TestHelper.fakeChannel({
id: channelId,
team_id: teamId,
total_msg_count: 0,
} as Channel;
const channelMember: ChannelMembership = {
});
const channelMember: ChannelMembership = TestHelper.fakeChannelMember({
id: 'id',
channel_id: channelId,
msg_count: 0,
} as ChannelMembership;
});
it('handle not found database', async () => {
const {models, error} = await storePostsForChannel('foo', channelId, [post], [post.id], '', ActionType.POSTS.RECEIVED_IN_CHANNEL, [user], false);

View file

@ -3,6 +3,7 @@
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
import TestHelper from '@test/test_helper';
import {
setCurrentUserStatus,
@ -13,8 +14,6 @@ import {
} from './user';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
const serverUrl = 'baseHandler.test.com';
let operator: ServerDataOperator;
@ -27,11 +26,11 @@ jest.mock('@init/credentials', () => {
};
});
const user: UserProfile = {
const user: UserProfile = TestHelper.fakeUser({
id: 'userid',
username: 'username',
roles: '',
} as UserProfile;
});
beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
@ -44,12 +43,12 @@ afterEach(async () => {
describe('setCurrentUserStatus', () => {
it('handle not found database', async () => {
const result = await setCurrentUserStatus('foo', '') as {error: unknown};
const result = await setCurrentUserStatus('foo', '');
expect(result?.error).toBeDefined();
});
it('handle no user', async () => {
const result = await setCurrentUserStatus(serverUrl, '') as {error: unknown};
const result = await setCurrentUserStatus(serverUrl, '');
expect(result?.error).toBeDefined();
expect((result?.error as Error).message).toBe(`No current user for ${serverUrl}`);
});
@ -58,20 +57,20 @@ describe('setCurrentUserStatus', () => {
await operator.handleUsers({users: [user], prepareRecordsOnly: false});
await operator.handleSystem({systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_USER_ID, value: user.id}], prepareRecordsOnly: false});
const result = await setCurrentUserStatus(serverUrl, 'away');
expect(result).toBeNull();
expect(result).not.toHaveProperty('error');
});
});
describe('updateLocalCustomStatus', () => {
it('handle not found database', async () => {
const result = await updateLocalCustomStatus('foo', {} as UserModel) as {error: unknown};
const result = await updateLocalCustomStatus('foo', TestHelper.fakeUserModel());
expect(result?.error).toBeDefined();
});
it('base case', async () => {
const userModels = await operator.handleUsers({users: [user], prepareRecordsOnly: false});
await operator.handleSystem({systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_USER_ID, value: user.id}], prepareRecordsOnly: false});
const result = await updateLocalCustomStatus(serverUrl, userModels[0], {text: 'customstatus'}) as {};
const result = await updateLocalCustomStatus(serverUrl, userModels[0], {text: 'customstatus'});
expect(result).toBeDefined();
expect(result).not.toHaveProperty('error');
});
@ -79,14 +78,13 @@ describe('updateLocalCustomStatus', () => {
describe('updateRecentCustomStatuses', () => {
it('handle not found database', async () => {
const result = await updateRecentCustomStatuses('foo', {text: 'customstatus'}) as {error: unknown};
expect(result?.error).toBeDefined();
const result = await updateRecentCustomStatuses('foo', {text: 'customstatus'});
expect(result.error).toBeDefined();
});
it('base case', async () => {
const result = await updateRecentCustomStatuses(serverUrl, {text: 'customstatus'}) as SystemModel[];
expect(result).toBeDefined();
expect(result.length).toBe(1); // system
const result = await updateRecentCustomStatuses(serverUrl, {text: 'customstatus'});
expect(result.models?.length).toBe(1); // system
});
});

View file

@ -12,7 +12,7 @@ import {addRecentReaction} from './reactions';
import type Model from '@nozbe/watermelondb/Model';
import type UserModel from '@typings/database/models/servers/user';
export async function setCurrentUserStatus(serverUrl: string, status: string) {
export async function setCurrentUserStatus(serverUrl: string, status: string): Promise<{error?: unknown}> {
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const user = await getCurrentUser(database);
@ -25,14 +25,14 @@ export async function setCurrentUserStatus(serverUrl: string, status: string) {
await operator.batchRecords([user], 'setCurrentUserStatus');
}
return null;
return {};
} catch (error) {
logError('Failed setCurrentUserStatus', error);
return {error};
}
}
export async function updateLocalCustomStatus(serverUrl: string, user: UserModel, customStatus?: UserCustomStatus) {
export async function updateLocalCustomStatus(serverUrl: string, user: UserModel, customStatus?: UserCustomStatus): Promise<{error?: unknown}> {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models: Model[] = [];
@ -43,9 +43,9 @@ export async function updateLocalCustomStatus(serverUrl: string, user: UserModel
models.push(userModel);
if (customStatus) {
const recent = await updateRecentCustomStatuses(serverUrl, customStatus, true);
if (Array.isArray(recent)) {
models.push(...recent);
const {models: customStatusModels} = await updateRecentCustomStatuses(serverUrl, customStatus, true);
if (customStatusModels?.length) {
models.push(...customStatusModels);
}
if (customStatus.emoji) {
@ -65,7 +65,7 @@ export async function updateLocalCustomStatus(serverUrl: string, user: UserModel
}
}
export const updateRecentCustomStatuses = async (serverUrl: string, customStatus: UserCustomStatus, prepareRecordsOnly = false, remove = false) => {
export const updateRecentCustomStatuses = async (serverUrl: string, customStatus: UserCustomStatus, prepareRecordsOnly = false, remove = false): Promise<{models?: Model[]; error?: unknown}> => {
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const recentStatuses = await getRecentCustomStatuses(database);
@ -83,13 +83,14 @@ export const updateRecentCustomStatuses = async (serverUrl: string, customStatus
recentStatuses.unshift(customStatus);
}
return operator.handleSystem({
const models = await operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.RECENT_CUSTOM_STATUS,
value: JSON.stringify(recentStatuses),
}],
prepareRecordsOnly,
});
return {models};
} catch (error) {
logError('Failed updateRecentCustomStatuses', error);
return {error};

View file

@ -880,7 +880,7 @@ export const getAllSupportedTimezones = async (serverUrl: string) => {
}
};
export const fetchCustomAttributes = async (serverUrl: string, userId: string, filterEmpty = false): Promise<{attributes: CustomAttributeSet; error: unknown}> => {
export const fetchCustomAttributes = async (serverUrl: string, userId: string, filterEmpty = false): Promise<{attributes: CustomAttributeSet; error?: unknown}> => {
try {
const client = NetworkManager.getClient(serverUrl);
const [fields, attrValues] = await Promise.all([
@ -901,13 +901,13 @@ export const fetchCustomAttributes = async (serverUrl: string, userId: string, f
};
}
});
return {attributes, error: undefined};
return {attributes};
}
return {attributes: {} as Record<string, CustomAttribute>, error: undefined};
return {attributes: {}};
} catch (error) {
logDebug('error on fetchCustomAttributes', getFullErrorMessage(error));
forceLogoutIfNecessary(serverUrl, error);
return {attributes: {} as Record<string, CustomAttribute>, error};
return {attributes: {}, error};
}
};

View file

@ -20,12 +20,10 @@ import {getIsCRTEnabled} from '@queries/servers/thread';
import {getCurrentUser} from '@queries/servers/user';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
import TestHelper from '@test/test_helper';
import {handleFirstConnect, handleReconnect} from './index';
import type PostModel from '@typings/database/models/servers/post';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@actions/local/channel');
jest.mock('@actions/local/systems');
jest.mock('@actions/remote/channel');
@ -87,10 +85,10 @@ describe('WebSocket Index Actions', () => {
};
jest.mocked(entry).mockResolvedValue(mockEntryData);
jest.mocked(getCurrentUser).mockResolvedValue({
jest.mocked(getCurrentUser).mockResolvedValue(TestHelper.fakeUserModel({
id: currentUserId,
locale: 'en',
} as UserModel);
}));
jest.mocked(getConfig).mockResolvedValue({Version: '9.0.0'} as ClientConfig);
jest.mocked(isSupportedServerCalls).mockReturnValue(true);
@ -107,10 +105,10 @@ describe('WebSocket Index Actions', () => {
it('should handle error when server database not found', async () => {
DatabaseManager.serverDatabases = {};
const error = await handleFirstConnect(serverUrl) as any;
const error = await handleFirstConnect(serverUrl);
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe('cannot find server database');
expect((error as Error).message).toBe('cannot find server database');
});
});
@ -137,10 +135,10 @@ describe('WebSocket Index Actions', () => {
};
jest.mocked(entry).mockResolvedValue(mockEntryData);
jest.mocked(getCurrentUser).mockResolvedValue({
jest.mocked(getCurrentUser).mockResolvedValue(TestHelper.fakeUserModel({
id: currentUserId,
locale: 'en',
} as UserModel);
}));
jest.mocked(getConfig).mockResolvedValue({Version: '9.0.0'} as ClientConfig);
jest.mocked(isSupportedServerCalls).mockReturnValue(true);
jest.mocked(getActiveServerUrl).mockResolvedValue(serverUrl);
@ -169,7 +167,7 @@ describe('WebSocket Index Actions', () => {
it('should fetch thread posts when CRT enabled', async () => {
const threadId = 'thread-id';
const lastPost = {id: 'post-id', createAt: 123} as PostModel;
const lastPost = TestHelper.fakePostModel({id: 'post-id', createAt: 123});
jest.mocked(NavigationStore.getScreensInStack).mockReturnValue(['Thread']);
@ -193,7 +191,7 @@ describe('WebSocket Index Actions', () => {
});
it('should handle notification tapped state', async () => {
(NavigationStore.getScreensInStack as jest.Mock).mockReturnValue(['Channel']);
jest.mocked(NavigationStore.getScreensInStack).mockReturnValue(['Channel']);
jest.mocked(EphemeralStore.wasNotificationTapped).mockReturnValue(true);
@ -206,10 +204,10 @@ describe('WebSocket Index Actions', () => {
it('should handle error in entry data', async () => {
jest.mocked(entry).mockResolvedValue({error: new Error('entry error')});
const result = await handleReconnect(serverUrl) as any;
const result = await handleReconnect(serverUrl);
expect(result).toBeInstanceOf(Error);
expect(result.message).toBe('entry error');
expect((result as Error).message).toBe('entry error');
});
});
});

View file

@ -6,7 +6,7 @@ import {DeviceEventEmitter} from 'react-native';
import {markChannelAsViewed, markChannelAsUnread, storeMyChannelsForTeam, updateLastPostAt} from '@actions/local/channel';
import {addPostAcknowledgement, markPostAsDeleted, removePostAcknowledgement} from '@actions/local/post';
import {updateThread} from '@actions/local/thread';
import {fetchChannelStats, fetchMyChannel, type MyChannelsRequest} from '@actions/remote/channel';
import {fetchChannelStats, fetchMyChannel} from '@actions/remote/channel';
import {fetchPostAuthors} from '@actions/remote/post';
import {fetchThread} from '@actions/remote/thread';
import {fetchMissingProfilesByIds} from '@actions/remote/user';
@ -19,17 +19,13 @@ import {getCurrentUserId, getCurrentChannelId} from '@queries/servers/system';
import {getIsCRTEnabled} from '@queries/servers/thread';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
import MyChannelModel from '@typings/database/models/servers/my_channel';
import TestHelper from '@test/test_helper';
import {isTablet} from '@utils/helpers';
import {shouldIgnorePost} from '@utils/post';
import {handleNewPostEvent, handlePostEdited, handlePostDeleted, handlePostUnread, handlePostAcknowledgementAdded, handlePostAcknowledgementRemoved} from './posts';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type ThreadModel from '@typings/database/models/servers/thread';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@queries/servers/post');
jest.mock('@queries/servers/channel');
@ -53,9 +49,9 @@ const serverUrl = 'baseHandler.test.com';
describe('WebSocket Post Actions', () => {
let operator: ServerDataOperator;
const post = {id: 'post1', channel_id: 'channel1', user_id: 'user1', create_at: 12345, message: 'hello'} as Post;
const postModels = [{channelId: post.channel_id, userId: post.user_id, message: post.message} as PostModel];
const myChannelModel = {id: 'channel1', manuallyUnread: false, messageCount: 4, mentionsCount: 0, lastViewedAt: 1} as MyChannelModel;
const post = TestHelper.fakePost({id: 'post1', channel_id: 'channel1', user_id: 'user1', create_at: 12345, message: 'hello'});
const postModels = [TestHelper.fakePostModel({channelId: post.channel_id, userId: post.user_id, message: post.message, isPinned: true})];
const myChannelModel = TestHelper.fakeMyChannelModel({id: 'channel1', manuallyUnread: false, messageCount: 4, mentionsCount: 0, lastViewedAt: 1});
const mockedGetPostById = jest.mocked(getPostById);
const mockedUpdateLastPostAt = jest.mocked(updateLastPostAt);
@ -105,13 +101,13 @@ describe('WebSocket Post Actions', () => {
mockedIsTablet.mockReturnValue(false);
it('should handle new post event - channel membership present', async () => {
const userModel = {id: 'user1', username: 'username1'} as UserModel;
const userModel = TestHelper.fakeUserModel({id: 'user1', username: 'username1'});
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
jest.spyOn(operator, 'handleUsers').mockResolvedValue([userModel]);
mockedFetchPostAuthors.mockResolvedValueOnce({authors: [{id: 'user1', username: 'username1'} as UserProfile]});
mockedFetchPostAuthors.mockResolvedValueOnce({authors: [TestHelper.fakeUser({id: 'user1', username: 'username1'})]});
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedShouldIgnorePost.mockReturnValue(false);
@ -120,12 +116,22 @@ describe('WebSocket Post Actions', () => {
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: undefined,
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([userModel, {_preparedState: null, id: 'channel1', lastViewedAt: 1, manuallyUnread: false, mentionsCount: 0, messageCount: 4}, postModels[0]], 'handleNewPostEvent');
expect(batchRecordsSpy).toHaveBeenCalledWith([
userModel,
expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}),
postModels[0],
], 'handleNewPostEvent');
});
it('should handle new post event - without channel membership present', async () => {
@ -134,7 +140,7 @@ describe('WebSocket Post Actions', () => {
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValueOnce(undefined);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [{user_id: 'user1', channel_id: 'channel1'}]} as MyChannelsRequest);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedStoreMyChannelsForTeam.mockResolvedValue({models: [myChannelModel], error: undefined});
mockedGetMyChannel.mockResolvedValueOnce(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
@ -144,7 +150,7 @@ describe('WebSocket Post Actions', () => {
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: undefined,
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
@ -157,7 +163,7 @@ describe('WebSocket Post Actions', () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
jest.spyOn(operator, 'handlePosts').mockResolvedValue(postModels);
mockedGetMyChannel.mockResolvedValue({...myChannelModel, manuallyUnread: true} as MyChannelModel);
mockedGetMyChannel.mockResolvedValue(TestHelper.fakeMyChannelModel({...myChannelModel, manuallyUnread: true}));
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedShouldIgnorePost.mockReturnValue(false);
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
@ -166,16 +172,22 @@ describe('WebSocket Post Actions', () => {
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: undefined,
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([{_preparedState: null, id: 'channel1', lastViewedAt: 1, manuallyUnread: false, mentionsCount: 0, messageCount: 4}, postModels[0]], 'handleNewPostEvent');
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}), postModels[0]], 'handleNewPostEvent');
});
it('should handle new post event - out of order ws, CRT on, root id', async () => {
const newPost = {...post, root_id: 'post2'} as Post;
const newPost = TestHelper.fakePost({...post, root_id: 'post2'});
jest.spyOn(EphemeralStore, 'getLastPostWebsocketEvent').mockReturnValueOnce({deleted: true, post: newPost});
const emitSpy = jest.spyOn(DeviceEventEmitter, 'emit');
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords');
@ -213,12 +225,18 @@ describe('WebSocket Post Actions', () => {
expect(mockedGetScreensInStack).toHaveBeenCalled();
expect(emitSpy).toHaveBeenCalledWith(Events.USER_STOP_TYPING, {
channelId: 'channel1',
rootId: undefined,
rootId: '',
userId: 'user1',
now: expect.any(Number),
});
expect(batchRecordsSpy).toHaveBeenCalledWith([{_preparedState: null, id: 'channel1', lastViewedAt: 1, manuallyUnread: false, mentionsCount: 0, messageCount: 4}, postModels[0]], 'handleNewPostEvent');
expect(batchRecordsSpy).toHaveBeenCalledWith([expect.objectContaining({
id: 'channel1',
lastViewedAt: 1,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 4,
}), postModels[0]], 'handleNewPostEvent');
});
it('should handle new post event - no operator', async () => {
@ -302,7 +320,7 @@ describe('WebSocket Post Actions', () => {
},
} as WebSocketMessage;
const threadModel = {viewedAt: 1, id: 'thread1'} as ThreadModel;
const threadModel = TestHelper.fakeThreadModel({viewedAt: 1, id: 'thread1'});
it('should handle post deleted event', async () => {
const batchRecordsSpy = jest.spyOn(operator, 'batchRecords').mockImplementation(jest.fn());
@ -311,7 +329,7 @@ describe('WebSocket Post Actions', () => {
mockedGetPostById.mockResolvedValue(postModels[0]);
mockedMarkPostAsDeleted.mockResolvedValue({model: postModels[0]});
mockedUpdateThread.mockResolvedValue({model: threadModel});
mockedGetChannelById.mockResolvedValue({id: 'channel1', teamId: 'team1'} as ChannelModel);
mockedGetChannelById.mockResolvedValue(TestHelper.fakeChannelModel({id: 'channel1', teamId: 'team1'}));
await handlePostDeleted(serverUrl, msg);
@ -372,7 +390,7 @@ describe('WebSocket Post Actions', () => {
it('should handle post unread event', async () => {
mockedGetMyChannel.mockResolvedValue(myChannelModel);
mockedGetIsCRTEnabled.mockResolvedValue(false);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [{user_id: 'user1', channel_id: 'channel1'}]} as MyChannelsRequest);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
await handlePostUnread(serverUrl, msg);
@ -381,9 +399,9 @@ describe('WebSocket Post Actions', () => {
});
it('should handle post unread event - CRT enabled, manually marked read', async () => {
mockedGetMyChannel.mockResolvedValue({...myChannelModel, manuallyUnread: true} as MyChannelModel);
mockedGetMyChannel.mockResolvedValue(TestHelper.fakeMyChannelModel({...myChannelModel, manuallyUnread: true}));
mockedGetIsCRTEnabled.mockResolvedValue(true);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [{user_id: 'user1', channel_id: 'channel1'}]} as MyChannelsRequest);
mockedFetchMyChannel.mockResolvedValue({teamId: 'team1', memberships: [TestHelper.fakeChannelMember({user_id: 'user1', channel_id: 'channel1'})]});
mockedMarkChannelAsUnread.mockResolvedValue({member: myChannelModel});
await handlePostUnread(serverUrl, msg);

View file

@ -6,12 +6,11 @@ import DatabaseManager from '@database/manager';
import {getRoleById} from '@queries/servers/role';
import {getCurrentUserId} from '@queries/servers/system';
import {getCurrentUser} from '@queries/servers/user';
import TestHelper from '@test/test_helper';
import {handleRoleUpdatedEvent, handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent} from './roles';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type RoleModel from '@typings/database/models/servers/role';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@actions/remote/role');
jest.mock('@database/manager');
@ -38,7 +37,7 @@ describe('WebSocket Roles Actions', () => {
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
batchRecords = jest.spyOn(operator, 'batchRecords').mockResolvedValue();
handleRole = jest.spyOn(operator, 'handleRole').mockResolvedValue([{id: 'role1'} as RoleModel]);
handleRole = jest.spyOn(operator, 'handleRole').mockResolvedValue([TestHelper.fakeRoleModel({id: 'role1'})]);
handleMyTeam = jest.spyOn(operator, 'handleMyTeam').mockResolvedValue([]);
handleTeamMemberships = jest.spyOn(operator, 'handleTeamMemberships').mockResolvedValue([]);
});
@ -77,7 +76,7 @@ describe('WebSocket Roles Actions', () => {
name: 'test_role',
permissions: ['permission1'],
};
jest.mocked(getRoleById).mockResolvedValue({id: roleId} as RoleModel);
jest.mocked(getRoleById).mockResolvedValue(TestHelper.fakeRoleModel({id: roleId}));
const msg = {
data: {
role: JSON.stringify(mockRole),
@ -93,7 +92,7 @@ describe('WebSocket Roles Actions', () => {
});
it('should handle invalid JSON in role data', async () => {
jest.mocked(getRoleById).mockResolvedValue({id: roleId} as RoleModel);
jest.mocked(getRoleById).mockResolvedValue(TestHelper.fakeRoleModel({id: roleId}));
const msg = {
data: {
role: 'invalid json',
@ -132,11 +131,11 @@ describe('WebSocket Roles Actions', () => {
it('should update roles and user', async () => {
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
jest.mocked(getCurrentUser).mockResolvedValue({
jest.mocked(getCurrentUser).mockResolvedValue(TestHelper.fakeUserModel({
prepareUpdate: jest.fn(),
} as unknown as UserModel);
}));
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
roles: [{id: 'role1'} as Role],
roles: [TestHelper.fakeRole({id: 'role1'})],
});
const msg = {
@ -156,7 +155,7 @@ describe('WebSocket Roles Actions', () => {
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
jest.mocked(getCurrentUser).mockResolvedValue(undefined);
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
roles: [{id: 'role1'} as Role],
roles: [TestHelper.fakeRole({id: 'role1'})],
});
const msg = {
@ -174,9 +173,9 @@ describe('WebSocket Roles Actions', () => {
it('should handle no new roles from fetchRolesIfNeeded', async () => {
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
jest.mocked(getCurrentUser).mockResolvedValue({
jest.mocked(getCurrentUser).mockResolvedValue(TestHelper.fakeUserModel({
prepareUpdate: jest.fn(),
} as unknown as UserModel);
}));
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
roles: [],
});
@ -246,7 +245,7 @@ describe('WebSocket Roles Actions', () => {
it('should update roles, myTeam and teamMembership', async () => {
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
roles: [{id: 'role1'} as Role],
roles: [TestHelper.fakeRole({id: 'role1'})],
});
const msg = {

View file

@ -13,11 +13,10 @@ import {getCurrentTeam, queryMyTeamsByIds} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
import EphemeralStore from '@store/ephemeral_store';
import {setTeamLoading} from '@store/team_load_store';
import TestHelper from '@test/test_helper';
import {logDebug} from '@utils/log';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type TeamModel from '@typings/database/models/servers/team';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@actions/local/team', () => ({
removeUserFromTeam: jest.fn(),
@ -102,10 +101,10 @@ describe('WebSocket Team Actions', () => {
const mockedUpdateUsersNoLongerVisible = jest.mocked(updateUsersNoLongerVisible);
const mockedUpdateCanJoinTeams = jest.mocked(updateCanJoinTeams);
mockedQueryMyTeamsByIds.mockReturnValue({fetch: jest.fn().mockResolvedValue([{id: 'team1'}])} as any);
mockedGetCurrentTeam.mockResolvedValue({id: 'team1'} as TeamModel);
mockedQueryMyTeamsByIds.mockReturnValue(TestHelper.fakeQuery([TestHelper.fakeMyTeamModel({id: 'team1'})]));
mockedGetCurrentTeam.mockResolvedValue(TestHelper.fakeTeamModel({id: 'team1'}));
mockedRemoveUserFromTeam.mockResolvedValue({error: undefined});
mockedGetCurrentUser.mockResolvedValue({id: 'user1', isGuest: true} as UserModel);
mockedGetCurrentUser.mockResolvedValue(TestHelper.fakeUserModel({id: 'user1', isGuest: true}));
mockedUpdateUsersNoLongerVisible.mockResolvedValue({error: undefined});
mockedUpdateCanJoinTeams.mockResolvedValue({error: undefined});
@ -121,7 +120,7 @@ describe('WebSocket Team Actions', () => {
const mockedUpdateCanJoinTeams = jest.mocked(updateCanJoinTeams);
const mockedRemoveUserFromTeam = jest.mocked(removeUserFromTeam);
mockedQueryMyTeamsByIds.mockReturnValue({fetch: jest.fn().mockResolvedValue([undefined])} as any);
mockedQueryMyTeamsByIds.mockReturnValue(TestHelper.fakeQuery([]));
mockedUpdateCanJoinTeams.mockResolvedValue({error: undefined});
await handleTeamArchived(serverUrl, msg);
@ -138,10 +137,10 @@ describe('WebSocket Team Actions', () => {
const mockedUpdateUsersNoLongerVisible = jest.mocked(updateUsersNoLongerVisible);
const mockedUpdateCanJoinTeams = jest.mocked(updateCanJoinTeams);
mockedQueryMyTeamsByIds.mockReturnValue({fetch: jest.fn().mockResolvedValue([{id: 'team1'}])} as any);
mockedGetCurrentTeam.mockResolvedValue({id: 'team2'} as TeamModel);
mockedQueryMyTeamsByIds.mockReturnValue(TestHelper.fakeQuery([TestHelper.fakeMyTeamModel({id: 'team1'})]));
mockedGetCurrentTeam.mockResolvedValue(TestHelper.fakeTeamModel({id: 'team2'}));
mockedRemoveUserFromTeam.mockResolvedValue({error: undefined});
mockedGetCurrentUser.mockResolvedValue({id: 'user1', isGuest: false} as UserModel);
mockedGetCurrentUser.mockResolvedValue(TestHelper.fakeUserModel({id: 'user1', isGuest: false}));
mockedUpdateCanJoinTeams.mockResolvedValue({error: undefined});
await handleTeamArchived(serverUrl, msg);
@ -214,8 +213,8 @@ describe('WebSocket Team Actions', () => {
const mockedGetCurrentTeam = jest.mocked(getCurrentTeam);
const mockedRemoveUserFromTeam = jest.mocked(removeUserFromTeam);
mockedGetCurrentUser.mockResolvedValue({id: 'user1', isGuest: true} as UserModel);
mockedGetCurrentTeam.mockResolvedValue({id: 'team1'} as TeamModel);
mockedGetCurrentUser.mockResolvedValue(TestHelper.fakeUserModel({id: 'user1', isGuest: true}));
mockedGetCurrentTeam.mockResolvedValue(TestHelper.fakeTeamModel({id: 'team1'}));
await handleLeaveTeamEvent(serverUrl, msg);
@ -226,7 +225,7 @@ describe('WebSocket Team Actions', () => {
const mockedGetCurrentUser = jest.mocked(getCurrentUser);
const mockedRemoveUserFromTeam = jest.mocked(removeUserFromTeam);
mockedGetCurrentUser.mockResolvedValue({id: 'user2', isGuest: true} as UserModel);
mockedGetCurrentUser.mockResolvedValue(TestHelper.fakeUserModel({id: 'user2', isGuest: true}));
await handleLeaveTeamEvent(serverUrl, msg);
@ -249,8 +248,8 @@ describe('WebSocket Team Actions', () => {
const mockedGetCurrentTeam = jest.mocked(getCurrentTeam);
const mockedRemoveUserFromTeam = jest.mocked(removeUserFromTeam);
mockedGetCurrentUser.mockResolvedValue({id: 'user1', isGuest: false} as UserModel);
mockedGetCurrentTeam.mockResolvedValue({id: 'team2'} as TeamModel);
mockedGetCurrentUser.mockResolvedValue(TestHelper.fakeUserModel({id: 'user1', isGuest: false}));
mockedGetCurrentTeam.mockResolvedValue(TestHelper.fakeTeamModel({id: 'team2'}));
await handleLeaveTeamEvent(serverUrl, msg);

View file

@ -13,11 +13,11 @@ import {queryChannelsByTypes, queryUserChannelsByTypes} from '@queries/servers/c
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {getConfig, getLicense} from '@queries/servers/system';
import {getCurrentUser} from '@queries/servers/user';
import TestHelper from '@test/test_helper';
import {handleUserUpdatedEvent, handleUserTypingEvent, handleStatusChangedEvent, userTyping} from './users';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@actions/local/channel');
jest.mock('@actions/local/user');
@ -46,13 +46,11 @@ describe('WebSocket Users Actions', () => {
await DatabaseManager.init([serverUrl]);
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
jest.mocked(queryChannelsByTypes).mockReturnValue({
fetch: jest.fn().mockResolvedValue([]),
} as any);
jest.mocked(queryChannelsByTypes).mockReturnValue(TestHelper.fakeQuery([]));
DatabaseManager.getActiveServerUrl = jest.fn().mockResolvedValue(serverUrl);
batchRecords = jest.spyOn(operator, 'batchRecords').mockResolvedValue();
handleUsers = jest.spyOn(operator, 'handleUsers').mockResolvedValue([{id: 'user1'} as UserModel]);
handleUsers = jest.spyOn(operator, 'handleUsers').mockResolvedValue([TestHelper.fakeUserModel({id: 'user1'})]);
});
afterEach(async () => {
@ -84,17 +82,17 @@ describe('WebSocket Users Actions', () => {
});
it('should handle current user update', async () => {
const mockUser = {
const mockUser = TestHelper.fakeUser({
id: currentUserId,
update_at: 1234,
notify_props: {email: 'true'},
} as UserProfile;
notify_props: TestHelper.fakeUserNotifyProps({email: 'true'}),
});
const mockCurrentUser = {
const mockCurrentUser = TestHelper.fakeUserModel({
id: currentUserId,
updateAt: 1000,
locale: 'en',
} as UserModel;
});
jest.mocked(getCurrentUser).mockResolvedValue(mockCurrentUser);
jest.mocked(fetchMe).mockResolvedValue({user: mockUser});
@ -112,22 +110,21 @@ describe('WebSocket Users Actions', () => {
});
it('should handle other user update', async () => {
const mockUser = {
const mockUser = TestHelper.fakeUser({
id: otherUserId,
update_at: 1234,
};
});
const mockCurrentUser = {
const mockCurrentUser = TestHelper.fakeUserModel({
id: currentUserId,
updateAt: 1000,
} as UserModel;
});
jest.mocked(getCurrentUser).mockResolvedValue(mockCurrentUser);
jest.mocked(queryUserChannelsByTypes).mockReturnValue({
fetch: jest.fn().mockResolvedValue([{id: 'channel-1'}]),
} as any);
jest.mocked(queryUserChannelsByTypes).mockReturnValue(
TestHelper.fakeQuery([TestHelper.fakeChannelModel({id: 'channel-1'})]));
jest.mocked(updateChannelsDisplayName).mockResolvedValue({models: []});
handleUsers.mockResolvedValue([{id: 'model-1'}]);
handleUsers.mockResolvedValue([TestHelper.fakeUserModel({id: 'model-1'})]);
const msg = {
data: {
@ -209,15 +206,13 @@ describe('WebSocket Users Actions', () => {
jest.mocked(getConfig).mockResolvedValue(mockConfig as any);
jest.mocked(fetchUsersByIds).mockResolvedValue({
users: [{id: otherUserId, username: 'other-user'} as UserProfile],
users: [TestHelper.fakeUser({id: otherUserId, username: 'other-user'})],
existingUsers: [],
});
jest.mocked(queryDisplayNamePreferences).mockReturnValue({
fetch: jest.fn().mockResolvedValue([{
value: 'full_name',
}]),
} as any);
jest.mocked(queryDisplayNamePreferences).mockReturnValue(TestHelper.fakeQuery([TestHelper.fakePreferenceModel({
value: 'full_name',
})]));
jest.mocked(getLicense).mockResolvedValue({} as ClientLicense);

View file

@ -114,7 +114,7 @@ const CombinedUserActivity = ({
} else {
showModalOverCurrentContext(Screens.POST_OPTIONS, passProps, bottomSheetModalOptions(theme));
}
}, [post, canDelete, isTablet, intl, location]);
}, [canDelete, post, location, isTablet, intl, theme]);
const renderMessage = (postType: string, userIds: string[], actorId?: string) => {
if (!post) {
@ -156,10 +156,10 @@ const CombinedUserActivity = ({
(userIds[0] === currentUserId || userIds[0] === currentUsername) &&
secureGetFromRecord(postTypeMessages, postType)?.one_you
) {
localeHolder = postTypeMessages[postType].one_you;
localeHolder = postTypeMessages[postType as keyof typeof postTypeMessages].one_you;
}
} else {
localeHolder = postTypeMessages[postType].two;
localeHolder = postTypeMessages[postType as keyof typeof postTypeMessages].two;
}
// We default to empty string, but this should never happen

View file

@ -12,12 +12,12 @@ export default {
CRT_CHUNK_SIZE: 60,
STATUS_INTERVAL: 60000,
AUTOCOMPLETE_LIMIT_DEFAULT: 25,
MENTION: 'mention',
OUT_OF_OFFICE: 'ooo',
OFFLINE: 'offline',
AWAY: 'away',
ONLINE: 'online',
DND: 'dnd',
MENTION: 'mention' as const,
OUT_OF_OFFICE: 'ooo' as const,
OFFLINE: 'offline' as const,
AWAY: 'away' as const,
ONLINE: 'online' as const,
DND: 'dnd' as const,
STATUS_COMMANDS: ['offline', 'away', 'online', 'dnd'],
DEFAULT_CHANNEL: 'town-square',
DM_CHANNEL: 'D' as const,

View file

@ -3,7 +3,7 @@
import {toMilliseconds} from '@utils/datetime';
export const PostTypes: Record<string, string> = {
export const PostTypes = {
CHANNEL_DELETED: 'system_channel_deleted',
CHANNEL_UNARCHIVED: 'system_channel_restored',
DISPLAYNAME_CHANGE: 'system_displayname_change',
@ -35,7 +35,7 @@ export const PostTypes: Record<string, string> = {
SYSTEM_AUTO_RESPONDER: 'system_auto_responder',
CUSTOM_CALLS: 'custom_calls',
CUSTOM_CALLS_RECORDING: 'custom_calls_recording',
};
} as const;
export const PostPriorityColors = {
URGENT: '#D24B4E',

View file

@ -18,9 +18,9 @@ const {INFO, GLOBAL} = MM_TABLES.APP;
* @param {RecordPair} operator.value
* @returns {Promise<Model>}
*/
export const transformInfoRecord = ({action, database, value}: TransformerArgs): Promise<Model> => {
const raw = value.raw as AppInfo;
const record = value.record as InfoModel | undefined;
export const transformInfoRecord = ({action, database, value}: TransformerArgs<InfoModel, AppInfo>): Promise<Model> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
const fieldsMapper = (app: InfoModel) => {
@ -46,8 +46,8 @@ export const transformInfoRecord = ({action, database, value}: TransformerArgs):
* @param {RecordPair} operator.value
* @returns {Promise<Model>}
*/
export const transformGlobalRecord = ({action, database, value}: TransformerArgs): Promise<Model> => {
const raw = value.raw as IdValue;
export const transformGlobalRecord = ({action, database, value}: TransformerArgs<GlobalModel, IdValue>): Promise<Model> => {
const raw = value.raw;
const fieldsMapper = (global: GlobalModel) => {
global._raw.id = raw?.id;

View file

@ -21,10 +21,10 @@ import type {
export interface BaseDataOperatorType {
database: Database;
handleRecords: <T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs<T>, description: string) => Promise<Model[]>;
processRecords: <T extends Model>({createOrUpdateRawValues, deleteRawValues, tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs) => Promise<ProcessRecordResults<T>>;
handleRecords: <T extends Model, R extends RawValue>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs<T, R>, description: string) => Promise<Model[]>;
processRecords: <T extends Model, R extends RawValue>({createOrUpdateRawValues, deleteRawValues, tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs<R>) => Promise<ProcessRecordResults<T, R>>;
batchRecords: (models: Model[], description: string) => Promise<void>;
prepareRecords: <T extends Model>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T>) => Promise<Model[]>;
prepareRecords: <T extends Model, R extends RawValue>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T, R>) => Promise<Model[]>;
}
export default class BaseDataOperator {
@ -45,8 +45,8 @@ export default class BaseDataOperator {
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.buildKeyRecordBy
* @returns {Promise<{ProcessRecordResults<T>}>}
*/
processRecords = async <T extends Model>({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName, shouldUpdate}: ProcessRecordsArgs): Promise<ProcessRecordResults<T>> => {
const getRecords = async (rawValues: RawValue[]) => {
processRecords = async <T extends Model, R extends RawValue>({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName, shouldUpdate}: ProcessRecordsArgs<R>): Promise<ProcessRecordResults<T, R>> => {
const getRecords = async (rawValues: R[]) => {
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
const columnValues: string[] = getRangeOfValues({fieldName, raws: rawValues});
@ -69,8 +69,8 @@ export default class BaseDataOperator {
return existingRecords;
};
const createRaws: RecordPair[] = [];
const updateRaws: RecordPair[] = [];
const createRaws: Array<RecordPair<T, R>> = [];
const updateRaws: Array<RecordPair<T, R>> = [];
// for delete flow
const deleteRaws = await getRecords(deleteRawValues);
@ -97,7 +97,7 @@ export default class BaseDataOperator {
}
// Some raw value has an update_at field. We'll proceed to update only if the update_at value is different from the record's value in database
const updateRecords = getValidRecordsForUpdate({
const updateRecords = getValidRecordsForUpdate<T, R>({
tableName,
existingRecord,
newValue: newElement,
@ -129,7 +129,7 @@ export default class BaseDataOperator {
* @param {(TransformerArgs) => Promise<T extends Model>;} transformer
* @returns {Promise<T extends Model[]>}
*/
prepareRecords = async <T extends Model>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T>): Promise<T[]> => {
prepareRecords = async <T extends Model, R extends RawValue>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T, R>): Promise<T[]> => {
if (!this.database) {
logWarning('Database not defined in prepareRecords');
return [];
@ -140,7 +140,7 @@ export default class BaseDataOperator {
// create operation
if (createRaws?.length) {
const recordPromises = createRaws.map(
(createRecord: RecordPair) => {
(createRecord: RecordPair<T, R>) => {
return transformer({
database: this.database,
tableName,
@ -156,7 +156,7 @@ export default class BaseDataOperator {
// update operation
if (updateRaws?.length) {
const recordPromises = updateRaws.map(
(updateRecord: RecordPair) => {
(updateRecord: RecordPair<T, R>) => {
return transformer({
database: this.database,
tableName,
@ -209,7 +209,7 @@ export default class BaseDataOperator {
* @param {string} handleRecordsArgs.tableName
* @returns {Promise<Model[]>}
*/
async handleRecords<T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true, shouldUpdate}: HandleRecordsArgs<T>, description: string): Promise<T[]> {
async handleRecords<T extends Model, R extends RawValue>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true, shouldUpdate}: HandleRecordsArgs<T, R>, description: string): Promise<T[]> {
if (!createOrUpdateRawValues.length && !deleteRawValues.length) {
logWarning(
`An empty "rawValues" array has been passed to the handleRecords method for tableName ${tableName}`,
@ -217,7 +217,7 @@ export default class BaseDataOperator {
return [];
}
const {createRaws, deleteRaws, updateRaws} = await this.processRecords<T>({
const {createRaws, deleteRaws, updateRaws} = await this.processRecords<T, R>({
createOrUpdateRawValues,
deleteRawValues,
tableName,
@ -227,7 +227,7 @@ export default class BaseDataOperator {
});
let models: T[] = [];
models = await this.prepareRecords<T>({
models = await this.prepareRecords<T, R>({
tableName,
createRaws,
updateRaws,

View file

@ -44,7 +44,7 @@ export interface ChannelHandlerMix {
handleChannelMembership: ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => Promise<ChannelMembershipModel[]>;
handleMyChannelSettings: ({settings, prepareRecordsOnly}: HandleMyChannelSettingsArgs) => Promise<MyChannelSettingsModel[]>;
handleChannelInfo: ({channelInfos, prepareRecordsOnly}: HandleChannelInfoArgs) => Promise<ChannelInfoModel[]>;
handleMyChannel: ({channels, myChannels, isCRTEnabled, prepareRecordsOnly}: HandleMyChannelArgs) => Promise<Model[]>;
handleMyChannel: ({channels, myChannels, isCRTEnabled, prepareRecordsOnly}: HandleMyChannelArgs) => Promise<MyChannelModel[]>;
}
const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(superclass: TBase) => class extends superclass {
@ -63,7 +63,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
return [];
}
const uniqueRaws = getUniqueRawsBy({raws: channels, key: 'id'}) as Channel[];
const uniqueRaws = getUniqueRawsBy({raws: channels, key: 'id'});
const keys = uniqueRaws.map((c) => c.id);
const db: Database = this.database;
const existing = await db.get<ChannelModel>(CHANNEL).query(
@ -113,7 +113,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
return [];
}
const uniqueRaws = getUniqueRawsBy({raws: settings, key: 'id'}) as ChannelMembership[];
const uniqueRaws = getUniqueRawsBy({raws: settings, key: 'id'});
const keys = uniqueRaws.map((c) => c.channel_id);
const db: Database = this.database;
const existing = await db.get<MyChannelSettingsModel>(MY_CHANNEL_SETTINGS).query(
@ -173,7 +173,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
const uniqueRaws = getUniqueRawsBy({
raws: channelInfos as ChannelInfo[],
key: 'id',
}) as ChannelInfo[];
});
const keys = uniqueRaws.map((ci) => ci.id);
const db: Database = this.database;
const existing = await db.get<ChannelInfoModel>(CHANNEL_INFO).query(
@ -221,7 +221,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
* @param {boolean} myChannelsArgs.prepareRecordsOnly
* @returns {Promise<MyChannelModel[]>}
*/
handleMyChannel = async ({channels, myChannels, isCRTEnabled, prepareRecordsOnly = true}: HandleMyChannelArgs): Promise<Model[]> => {
handleMyChannel = async ({channels, myChannels, isCRTEnabled, prepareRecordsOnly = true}: HandleMyChannelArgs): Promise<MyChannelModel[]> => {
if (!myChannels?.length) {
logWarning(
'An empty or undefined "myChannels" array has been passed to the handleMyChannel method',
@ -261,7 +261,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
const uniqueRaws = getUniqueRawsBy({
raws: myChannels,
key: 'id',
}) as ChannelMembership[];
});
const ids = uniqueRaws.map((cm: ChannelMembership) => cm.channel_id);
const db: Database = this.database;
const existing = await db.get<MyChannelModel>(MY_CHANNEL).query(
@ -322,7 +322,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
id: `${m.channel_id}-${m.user_id}`,
}));
const uniqueRaws = getUniqueRawsBy({raws: memberships, key: 'id'}) as ChannelMember[];
const uniqueRaws = getUniqueRawsBy({raws: memberships, key: 'id'});
const ids = uniqueRaws.map((cm: ChannelMember) => `${cm.channel_id}-${cm.user_id}`);
const db: Database = this.database;
const existing = await db.get<ChannelMembershipModel>(CHANNEL_MEMBERSHIP).query(
@ -372,7 +372,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
return [];
}
const uniqueRaws = getUniqueRawsBy({raws: bookmarks, key: 'id'}) as ChannelBookmarkWithFileInfo[];
const uniqueRaws = getUniqueRawsBy({raws: bookmarks, key: 'id'});
const keys = uniqueRaws.map((c) => c.id);
const db: Database = this.database;
const existing = await db.get<ChannelBookmarkModel>(CHANNEL_BOOKMARK).query(

View file

@ -179,7 +179,7 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
return res;
}, {createOrUpdateFiles: [], deleteFiles: []});
const processedFiles = (await this.processRecords<FileModel>({
const processedFiles = (await this.processRecords<FileModel, FileInfo>({
createOrUpdateRawValues: raws.createOrUpdateFiles,
tableName: FILE,
fieldName: 'id',
@ -187,7 +187,7 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
shouldUpdate: shouldUpdateFileRecord,
}));
const preparedFiles = await this.prepareRecords<FileModel>({
const preparedFiles = await this.prepareRecords<FileModel, FileInfo>({
createRaws: processedFiles.createRaws,
updateRaws: processedFiles.updateRaws,
deleteRaws: processedFiles.deleteRaws,
@ -215,7 +215,7 @@ export default class ServerDataOperatorBase extends BaseDataOperator {
* @param {(TransformerArgs) => Promise<Model>} execute.recordOperator
* @returns {Promise<void>}
*/
async execute<T extends Model>({createRaws, transformer, tableName, updateRaws}: OperationArgs<T>, description: string): Promise<T[]> {
async execute<T extends Model, R extends RawValue>({createRaws, transformer, tableName, updateRaws}: OperationArgs<T, R>, description: string): Promise<T[]> {
const models = await this.prepareRecords({
tableName,
createRaws,

View file

@ -601,15 +601,15 @@ const PostHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercla
return [];
}
const update: RecordPair[] = [];
const update: Array<RecordPair<PostsInThreadModel, PostsInThread>> = [];
const create: PostsInThread[] = [];
const ids = Object.keys(postsMap);
for await (const rootId of ids) {
const {firstPost, lastPost} = getPostListEdges(postsMap[rootId]);
const chunks = (await this.database.get(POSTS_IN_THREAD).query(
const chunks = (await this.database.get<PostsInThreadModel>(POSTS_IN_THREAD).query(
Q.where('root_id', rootId),
Q.sortBy('latest', Q.desc),
).fetch()) as PostsInThreadModel[];
).fetch());
if (chunks.length) {
const chunk = chunks[0];
@ -633,12 +633,12 @@ const PostHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercla
}
}
const postInThreadRecords = (await this.prepareRecords({
const postInThreadRecords = await this.prepareRecords<PostsInThreadModel, PostsInThread>({
createRaws: getRawRecordPairs(create),
updateRaws: update,
transformer: transformPostInThreadRecord,
tableName: POSTS_IN_THREAD,
})) as PostsInThreadModel[];
});
if (postInThreadRecords?.length && !prepareRecordsOnly) {
await this.batchRecords(postInThreadRecords, 'handleReceivedPostsInThread');

View file

@ -38,7 +38,7 @@ const TeamThreadsSyncHandler = <TBase extends Constructor<ServerDataOperatorBase
}, {});
const create: TeamThreadsSync[] = [];
const update: RecordPair[] = [];
const update: Array<RecordPair<TeamThreadsSyncModel, TeamThreadsSync>> = [];
for await (const item of uniqueRaws) {
const {id} = item;
@ -54,12 +54,12 @@ const TeamThreadsSyncHandler = <TBase extends Constructor<ServerDataOperatorBase
}
}
const models = (await this.prepareRecords({
const models = await this.prepareRecords<TeamThreadsSyncModel, TeamThreadsSync>({
createRaws: getRawRecordPairs(create),
updateRaws: update,
transformer: transformTeamThreadsSyncRecord,
tableName: TEAM_THREADS_SYNC,
})) as TeamThreadsSyncModel[];
});
if (models?.length && !prepareRecordsOnly) {
await this.batchRecords(models, 'handleTeamThreadsSync');

View file

@ -20,19 +20,22 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<CategoryModel>}
*/
export const transformCategoryRecord = ({action, database, value}: TransformerArgs): Promise<CategoryModel> => {
const raw = value.raw as Category;
const record = value.record as CategoryModel;
export const transformCategoryRecord = ({action, database, value}: TransformerArgs<CategoryModel, Category>) => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// id of category comes from server response
const fieldsMapper = (category: CategoryModel) => {
category._raw.id = isCreateAction ? (raw?.id ?? category.id) : record.id;
category._raw.id = isCreateAction ? (raw?.id ?? category.id) : record!.id;
category.displayName = raw.display_name;
category.sorting = raw.sorting || 'recent';
category.sortOrder = raw.sort_order === 0 ? 0 : raw.sort_order / 10; // Sort order from server is in multiples of 10
category.muted = raw.muted ?? false;
category.collapsed = isCreateAction ? false : record.collapsed;
category.collapsed = isCreateAction ? false : record!.collapsed;
category.type = raw.type;
category.teamId = raw.team_id;
};
@ -43,7 +46,7 @@ export const transformCategoryRecord = ({action, database, value}: TransformerAr
tableName: CATEGORY,
value,
fieldsMapper,
}) as Promise<CategoryModel>;
});
};
/**
@ -53,14 +56,17 @@ export const transformCategoryRecord = ({action, database, value}: TransformerAr
* @param {RecordPair} operator.value
* @returns {Promise<CategoryChannelModel>}
*/
export const transformCategoryChannelRecord = ({action, database, value}: TransformerArgs): Promise<CategoryChannelModel> => {
const raw = value.raw as CategoryChannel;
const record = value.record as CategoryChannelModel;
export const transformCategoryChannelRecord = ({action, database, value}: TransformerArgs<CategoryChannelModel, CategoryChannel>) => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (categoryChannel: CategoryChannelModel) => {
categoryChannel._raw.id = isCreateAction ? (raw?.id ?? categoryChannel.id) : record.id;
categoryChannel._raw.id = isCreateAction ? (raw?.id ?? categoryChannel.id) : record!.id;
categoryChannel.channelId = raw.channel_id;
categoryChannel.categoryId = raw.category_id;
categoryChannel.sortOrder = raw.sort_order;
@ -72,5 +78,5 @@ export const transformCategoryChannelRecord = ({action, database, value}: Transf
tableName: CATEGORY_CHANNEL,
value,
fieldsMapper,
}) as Promise<CategoryChannelModel>;
});
};

View file

@ -98,7 +98,6 @@ describe('*** CHANNEL Prepare Records Test ***', () => {
record: undefined,
raw: {
id: 'c',
channel_id: 'c',
guest_count: 10,
header: 'channel info header',
member_count: 10,

View file

@ -29,14 +29,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<ChannelModel>}
*/
export const transformChannelRecord = ({action, database, value}: TransformerArgs): Promise<ChannelModel> => {
const raw = value.raw as Channel;
const record = value.record as ChannelModel;
export const transformChannelRecord = ({action, database, value}: TransformerArgs<ChannelModel, Channel>): Promise<ChannelModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (channel: ChannelModel) => {
channel._raw.id = isCreateAction ? (raw?.id ?? channel.id) : record.id;
channel._raw.id = isCreateAction ? (raw?.id ?? channel.id) : record!.id;
channel.createAt = raw.create_at;
channel.creatorId = raw.creator_id;
channel.deleteAt = raw.delete_at;
@ -56,7 +59,7 @@ export const transformChannelRecord = ({action, database, value}: TransformerArg
tableName: CHANNEL,
value,
fieldsMapper,
}) as Promise<ChannelModel>;
});
};
/**
@ -66,13 +69,16 @@ export const transformChannelRecord = ({action, database, value}: TransformerArg
* @param {RecordPair} operator.value
* @returns {Promise<MyChannelSettingsModel>}
*/
export const transformMyChannelSettingsRecord = ({action, database, value}: TransformerArgs): Promise<MyChannelSettingsModel> => {
const raw = value.raw as ChannelMembership;
const record = value.record as MyChannelSettingsModel;
export const transformMyChannelSettingsRecord = ({action, database, value}: TransformerArgs<MyChannelSettingsModel, ChannelMembership>): Promise<MyChannelSettingsModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (myChannelSetting: MyChannelSettingsModel) => {
myChannelSetting._raw.id = isCreateAction ? (raw.channel_id || myChannelSetting.id) : record.id;
myChannelSetting._raw.id = isCreateAction ? (raw.channel_id || myChannelSetting.id) : record!.id;
myChannelSetting.notifyProps = raw.notify_props;
};
@ -82,7 +88,7 @@ export const transformMyChannelSettingsRecord = ({action, database, value}: Tran
tableName: MY_CHANNEL_SETTINGS,
value,
fieldsMapper,
}) as Promise<MyChannelSettingsModel>;
});
};
/**
@ -92,13 +98,16 @@ export const transformMyChannelSettingsRecord = ({action, database, value}: Tran
* @param {RecordPair} operator.value
* @returns {Promise<ChannelInfoModel>}
*/
export const transformChannelInfoRecord = ({action, database, value}: TransformerArgs): Promise<ChannelInfoModel> => {
const raw = value.raw as Partial<ChannelInfo>;
const record = value.record as ChannelInfoModel;
export const transformChannelInfoRecord = ({action, database, value}: TransformerArgs<ChannelInfoModel, ChannelInfo>): Promise<ChannelInfoModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (channelInfo: ChannelInfoModel) => {
channelInfo._raw.id = isCreateAction ? (raw.id || channelInfo.id) : record.id;
channelInfo._raw.id = isCreateAction ? (raw.id || channelInfo.id) : record!.id;
channelInfo.guestCount = raw.guest_count ?? channelInfo.guestCount ?? 0;
channelInfo.header = raw.header ?? channelInfo.header ?? '';
channelInfo.memberCount = raw.member_count ?? channelInfo.memberCount ?? 0;
@ -113,7 +122,7 @@ export const transformChannelInfoRecord = ({action, database, value}: Transforme
tableName: CHANNEL_INFO,
value,
fieldsMapper,
}) as Promise<ChannelInfoModel>;
});
};
/**
@ -123,13 +132,16 @@ export const transformChannelInfoRecord = ({action, database, value}: Transforme
* @param {RecordPair} operator.value
* @returns {Promise<MyChannelModel>}
*/
export const transformMyChannelRecord = async ({action, database, value}: TransformerArgs): Promise<MyChannelModel> => {
const raw = value.raw as ChannelMembership;
const record = value.record as MyChannelModel;
export const transformMyChannelRecord = async ({action, database, value}: TransformerArgs<MyChannelModel, ChannelMembership>): Promise<MyChannelModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (myChannel: MyChannelModel) => {
myChannel._raw.id = isCreateAction ? (raw.channel_id || myChannel.id) : record.id;
myChannel._raw.id = isCreateAction ? (raw.channel_id || myChannel.id) : record!.id;
myChannel.roles = raw.roles;
// ignoring msg_count_root because msg_count, mention_count, last_post_at is already calculated in "handleMyChannel" based on CRT is enabled or not
@ -149,7 +161,7 @@ export const transformMyChannelRecord = async ({action, database, value}: Transf
tableName: MY_CHANNEL,
value,
fieldsMapper,
}) as Promise<MyChannelModel>;
});
};
/**
@ -159,14 +171,17 @@ export const transformMyChannelRecord = async ({action, database, value}: Transf
* @param {RecordPair} operator.value
* @returns {Promise<ChannelMembershipModel>}
*/
export const transformChannelMembershipRecord = ({action, database, value}: TransformerArgs): Promise<ChannelMembershipModel> => {
const raw = value.raw as ChannelMembership;
const record = value.record as ChannelMembershipModel;
export const transformChannelMembershipRecord = ({action, database, value}: TransformerArgs<ChannelMembershipModel, ChannelMembership>): Promise<ChannelMembershipModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (channelMember: ChannelMembershipModel) => {
channelMember._raw.id = isCreateAction ? (raw?.id ?? channelMember.id) : record.id;
channelMember._raw.id = isCreateAction ? (raw?.id ?? channelMember.id) : record!.id;
channelMember.channelId = raw.channel_id;
channelMember.userId = raw.user_id;
channelMember.schemeAdmin = raw.scheme_admin ?? false;
@ -178,7 +193,7 @@ export const transformChannelMembershipRecord = ({action, database, value}: Tran
tableName: CHANNEL_MEMBERSHIP,
value,
fieldsMapper,
}) as Promise<ChannelMembershipModel>;
});
};
/**
@ -188,14 +203,17 @@ export const transformChannelMembershipRecord = ({action, database, value}: Tran
* @param {RecordPair} operator.value
* @returns {Promise<ChannelBookmarkModel>}
*/
export const transformChannelBookmarkRecord = ({action, database, value}: TransformerArgs): Promise<ChannelBookmarkModel> => {
const raw = value.raw as ChannelBookmark;
const record = value.record as ChannelBookmarkModel;
export const transformChannelBookmarkRecord = ({action, database, value}: TransformerArgs<ChannelBookmarkModel, ChannelBookmark>): Promise<ChannelBookmarkModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (bookmark: ChannelBookmarkModel) => {
bookmark._raw.id = isCreateAction ? (raw?.id ?? bookmark.id) : record.id;
bookmark._raw.id = isCreateAction ? (raw?.id ?? bookmark.id) : record!.id;
bookmark.createAt = raw.create_at;
bookmark.deleteAt = raw.delete_at;
bookmark.updateAt = raw.update_at;
@ -219,5 +237,5 @@ export const transformChannelBookmarkRecord = ({action, database, value}: Transf
tableName: CHANNEL_BOOKMARK,
value,
fieldsMapper,
}) as Promise<ChannelBookmarkModel>;
});
};

View file

@ -47,7 +47,7 @@ describe('*** System Prepare Records Test ***', () => {
database: database!,
value: {
record: undefined,
raw: {id: 'system-1', name: 'system-name-1', value: 'system'},
raw: {id: 'system-1', value: 'system'},
},
});

View file

@ -26,14 +26,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<CustomEmojiModel>}
*/
export const transformCustomEmojiRecord = ({action, database, value}: TransformerArgs): Promise<CustomEmojiModel> => {
const raw = value.raw as CustomEmoji;
const record = value.record as CustomEmojiModel;
export const transformCustomEmojiRecord = ({action, database, value}: TransformerArgs<CustomEmojiModel, CustomEmoji>): Promise<CustomEmojiModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (emoji: CustomEmojiModel) => {
emoji._raw.id = isCreateAction ? (raw?.id ?? emoji.id) : record.id;
emoji._raw.id = isCreateAction ? (raw?.id ?? emoji.id) : record!.id;
emoji.name = raw.name;
};
@ -43,7 +46,7 @@ export const transformCustomEmojiRecord = ({action, database, value}: Transforme
tableName: CUSTOM_EMOJI,
value,
fieldsMapper,
}) as Promise<CustomEmojiModel>;
});
};
/**
@ -53,14 +56,17 @@ export const transformCustomEmojiRecord = ({action, database, value}: Transforme
* @param {RecordPair} operator.value
* @returns {Promise<RoleModel>}
*/
export const transformRoleRecord = ({action, database, value}: TransformerArgs): Promise<RoleModel> => {
const raw = value.raw as Role;
const record = value.record as RoleModel;
export const transformRoleRecord = ({action, database, value}: TransformerArgs<RoleModel, Role>): Promise<RoleModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (role: RoleModel) => {
role._raw.id = isCreateAction ? (raw?.id ?? role.id) : record.id;
role._raw.id = isCreateAction ? (raw?.id ?? role.id) : record!.id;
role.name = raw?.name;
role.permissions = raw?.permissions;
};
@ -71,7 +77,7 @@ export const transformRoleRecord = ({action, database, value}: TransformerArgs):
tableName: ROLE,
value,
fieldsMapper,
}) as Promise<RoleModel>;
});
};
/**
@ -81,8 +87,8 @@ export const transformRoleRecord = ({action, database, value}: TransformerArgs):
* @param {RecordPair} operator.value
* @returns {Promise<SystemModel>}
*/
export const transformSystemRecord = ({action, database, value}: TransformerArgs): Promise<SystemModel> => {
const raw = value.raw as IdValue;
export const transformSystemRecord = ({action, database, value}: TransformerArgs<SystemModel, IdValue>): Promise<SystemModel> => {
const raw = value.raw;
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (system: SystemModel) => {
@ -96,7 +102,7 @@ export const transformSystemRecord = ({action, database, value}: TransformerArgs
tableName: SYSTEM,
value,
fieldsMapper,
}) as Promise<SystemModel>;
});
};
/**
@ -106,8 +112,8 @@ export const transformSystemRecord = ({action, database, value}: TransformerArgs
* @param {RecordPair} operator.value
* @returns {Promise<ConfigModel>}
*/
export const transformConfigRecord = ({action, database, value}: TransformerArgs): Promise<ConfigModel> => {
const raw = value.raw as IdValue;
export const transformConfigRecord = ({action, database, value}: TransformerArgs<ConfigModel, IdValue>): Promise<ConfigModel> => {
const raw = value.raw;
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (config: ConfigModel) => {
@ -121,7 +127,7 @@ export const transformConfigRecord = ({action, database, value}: TransformerArgs
tableName: CONFIG,
value,
fieldsMapper,
}) as Promise<ConfigModel>;
});
};
/**
@ -131,14 +137,17 @@ export const transformConfigRecord = ({action, database, value}: TransformerArgs
* @param {RecordPair} operator.value
* @returns {Promise<FileModel>}
*/
export const transformFileRecord = ({action, database, value}: TransformerArgs): Promise<FileModel> => {
const raw = value.raw as FileInfo;
const record = value.record as FileModel;
export const transformFileRecord = ({action, database, value}: TransformerArgs<FileModel, FileInfo>): Promise<FileModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (file: FileModel) => {
file._raw.id = isCreateAction ? (raw.id || file.id) : record.id;
file._raw.id = isCreateAction ? (raw.id || file.id) : record!.id;
file.postId = raw.post_id || '';
file.name = raw.name;
file.extension = raw.extension;
@ -156,5 +165,5 @@ export const transformFileRecord = ({action, database, value}: TransformerArgs):
tableName: FILE,
value,
fieldsMapper,
}) as Promise<FileModel>;
});
};

View file

@ -26,14 +26,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<GroupModel>}
*/
export const transformGroupRecord = ({action, database, value}: TransformerArgs): Promise<GroupModel> => {
const raw = value.raw as Group;
const record = value.record as GroupModel;
export const transformGroupRecord = ({action, database, value}: TransformerArgs<GroupModel, Group>): Promise<GroupModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// id of group comes from server response
const fieldsMapper = (group: GroupModel) => {
group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record.id;
group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record!.id;
group.name = raw.name;
group.displayName = raw.display_name;
group.source = raw.source;
@ -47,7 +50,7 @@ export const transformGroupRecord = ({action, database, value}: TransformerArgs)
tableName: GROUP,
value,
fieldsMapper,
}) as Promise<GroupModel>;
});
};
/**
@ -57,8 +60,8 @@ export const transformGroupRecord = ({action, database, value}: TransformerArgs)
* @param {RecordPair} operator.value
* @returns {Promise<GroupChannelModel>}
*/
export const transformGroupChannelRecord = ({action, database, value}: TransformerArgs): Promise<GroupChannelModel> => {
const raw = value.raw as GroupChannel;
export const transformGroupChannelRecord = ({action, database, value}: TransformerArgs<GroupChannelModel, GroupChannel>): Promise<GroupChannelModel> => {
const raw = value.raw;
// id of group comes from server response
const fieldsMapper = (model: GroupChannelModel) => {
@ -73,7 +76,7 @@ export const transformGroupChannelRecord = ({action, database, value}: Transform
tableName: GROUP_CHANNEL,
value,
fieldsMapper,
}) as Promise<GroupChannelModel>;
});
};
/**
@ -83,8 +86,8 @@ export const transformGroupChannelRecord = ({action, database, value}: Transform
* @param {RecordPair} operator.value
* @returns {Promise<GroupMembershipModel>}
*/
export const transformGroupMembershipRecord = ({action, database, value}: TransformerArgs): Promise<GroupMembershipModel> => {
const raw = value.raw as GroupMembership;
export const transformGroupMembershipRecord = ({action, database, value}: TransformerArgs<GroupMembershipModel, GroupMembership>): Promise<GroupMembershipModel> => {
const raw = value.raw;
// id of group comes from server response
const fieldsMapper = (model: GroupMembershipModel) => {
@ -99,7 +102,7 @@ export const transformGroupMembershipRecord = ({action, database, value}: Transf
tableName: GROUP_MEMBERSHIP,
value,
fieldsMapper,
}) as Promise<GroupMembershipModel>;
});
};
/**
@ -109,8 +112,8 @@ export const transformGroupMembershipRecord = ({action, database, value}: Transf
* @param {RecordPair} operator.value
* @returns {Promise<GroupTeamModel>}
*/
export const transformGroupTeamRecord = ({action, database, value}: TransformerArgs): Promise<GroupTeamModel> => {
const raw = value.raw as GroupTeam;
export const transformGroupTeamRecord = ({action, database, value}: TransformerArgs<GroupTeamModel, GroupTeam>): Promise<GroupTeamModel> => {
const raw = value.raw;
// id of group comes from server response
const fieldsMapper = (model: GroupTeamModel) => {
@ -125,5 +128,5 @@ export const transformGroupTeamRecord = ({action, database, value}: TransformerA
tableName: GROUP_TEAM,
value,
fieldsMapper,
}) as Promise<GroupTeamModel>;
});
};

View file

@ -17,17 +17,17 @@ import type {PrepareBaseRecordArgs} from '@typings/database/database';
* @param {((PrepareBaseRecordArgs) => void)} operatorBase.generator
* @returns {Promise<Model>}
*/
export const prepareBaseRecord = async ({
export const prepareBaseRecord = async <T extends Model, R extends RawValue>({
action,
database,
tableName,
value,
fieldsMapper,
}: PrepareBaseRecordArgs): Promise<Model> => {
}: PrepareBaseRecordArgs<T, R>) => {
if (action === OperationType.UPDATE) {
const record = value.record as Model;
const record = value.record!;
return record.prepareUpdate(() => fieldsMapper(record));
}
return database.collections.get(tableName!).prepareCreate(fieldsMapper);
return database.collections.get<T>(tableName!).prepareCreate(fieldsMapper);
};

View file

@ -86,7 +86,6 @@ describe('*** POST Prepare Records Test ***', () => {
value: {
record: undefined,
raw: {
id: 'ps81i4yypoo',
root_id: 'ps81iqbddesfby8jayz7owg4yypoo',
message: 'draft message',
channel_id: 'channel_idp23232e',

View file

@ -24,14 +24,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<PostModel>}
*/
export const transformPostRecord = ({action, database, value}: TransformerArgs): Promise<PostModel> => {
const raw = value.raw as Post;
const record = value.record as PostModel;
export const transformPostRecord = ({action, database, value}: TransformerArgs<PostModel, Post>): Promise<PostModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (post: PostModel) => {
post._raw.id = isCreateAction ? (raw?.id ?? post.id) : record.id;
post._raw.id = isCreateAction ? (raw?.id ?? post.id) : record!.id;
post.channelId = raw.channel_id;
post.createAt = raw.create_at;
post.deleteAt = raw.delete_at || raw.delete_at === 0 ? raw?.delete_at : 0;
@ -61,7 +64,7 @@ export const transformPostRecord = ({action, database, value}: TransformerArgs):
tableName: POST,
value,
fieldsMapper,
}) as Promise<PostModel>;
});
};
/**
@ -71,13 +74,16 @@ export const transformPostRecord = ({action, database, value}: TransformerArgs):
* @param {RecordPair} operator.value
* @returns {Promise<PostsInThreadModel>}
*/
export const transformPostInThreadRecord = ({action, database, value}: TransformerArgs): Promise<PostsInThreadModel> => {
const raw = value.raw as PostsInThread;
const record = value.record as PostsInThreadModel;
export const transformPostInThreadRecord = ({action, database, value}: TransformerArgs<PostsInThreadModel, PostsInThread>) => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (postsInThread: PostsInThreadModel) => {
postsInThread._raw.id = isCreateAction ? (raw.id || postsInThread.id) : record.id;
postsInThread._raw.id = isCreateAction ? (raw.id || postsInThread.id) : record!.id;
postsInThread.rootId = raw.root_id;
postsInThread.earliest = raw.earliest;
postsInThread.latest = raw.latest!;
@ -89,7 +95,7 @@ export const transformPostInThreadRecord = ({action, database, value}: Transform
tableName: POSTS_IN_THREAD,
value,
fieldsMapper,
}) as Promise<PostsInThreadModel>;
});
};
/**
@ -99,10 +105,10 @@ export const transformPostInThreadRecord = ({action, database, value}: Transform
* @param {RecordPair} operator.value
* @returns {Promise<DraftModel>}
*/
export const transformDraftRecord = ({action, database, value}: TransformerArgs): Promise<DraftModel> => {
export const transformDraftRecord = ({action, database, value}: TransformerArgs<DraftModel, Draft>): Promise<DraftModel> => {
const emptyFileInfo: FileInfo[] = [];
const emptyPostMetadata: PostMetadata = {};
const raw = value.raw as Draft;
const raw = value.raw;
// We use the raw id as Draft is client side only and we would only be creating/deleting drafts
const fieldsMapper = (draft: DraftModel) => {
@ -121,7 +127,7 @@ export const transformDraftRecord = ({action, database, value}: TransformerArgs)
tableName: DRAFT,
value,
fieldsMapper,
}) as Promise<DraftModel>;
});
};
/**
@ -131,13 +137,16 @@ export const transformDraftRecord = ({action, database, value}: TransformerArgs)
* @param {RecordPair} operator.value
* @returns {Promise<PostsInChannelModel>}
*/
export const transformPostsInChannelRecord = ({action, database, value}: TransformerArgs): Promise<PostsInChannelModel> => {
const raw = value.raw as PostsInChannel;
const record = value.record as PostsInChannelModel;
export const transformPostsInChannelRecord = ({action, database, value}: TransformerArgs<PostsInChannelModel, PostsInChannel>): Promise<PostsInChannelModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (postsInChannel: PostsInChannelModel) => {
postsInChannel._raw.id = isCreateAction ? (raw.id || postsInChannel.id) : record.id;
postsInChannel._raw.id = isCreateAction ? (raw.id || postsInChannel.id) : record!.id;
postsInChannel.channelId = raw.channel_id;
postsInChannel.earliest = raw.earliest;
postsInChannel.latest = raw.latest;
@ -149,5 +158,5 @@ export const transformPostsInChannelRecord = ({action, database, value}: Transfo
tableName: POSTS_IN_CHANNEL,
value,
fieldsMapper,
}) as Promise<PostsInChannelModel>;
});
};

View file

@ -23,8 +23,6 @@ describe('*** REACTION Prepare Records Test ***', () => {
post_id: 'ps81iqbddesfby8jayz7owg4yypoo',
emoji_name: 'thumbsup',
create_at: 1596032651748,
update_at: 1608253011321,
delete_at: 0,
},
},
});

View file

@ -16,14 +16,17 @@ const {REACTION} = MM_TABLES.SERVER;
* @param {RecordPair} operator.value
* @returns {Promise<ReactionModel>}
*/
export const transformReactionRecord = ({action, database, value}: TransformerArgs): Promise<ReactionModel> => {
const raw = value.raw as Reaction;
const record = value.record as ReactionModel;
export const transformReactionRecord = ({action, database, value}: TransformerArgs<ReactionModel, Reaction>): Promise<ReactionModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// id of reaction comes from server response
const fieldsMapper = (reaction: ReactionModel) => {
reaction._raw.id = isCreateAction ? (raw?.id ?? reaction.id) : record.id;
reaction._raw.id = isCreateAction ? (raw?.id ?? reaction.id) : record!.id;
reaction.userId = raw.user_id;
reaction.postId = raw.post_id;
reaction.emojiName = raw.emoji_name;
@ -36,5 +39,5 @@ export const transformReactionRecord = ({action, database, value}: TransformerAr
tableName: REACTION,
value,
fieldsMapper,
}) as Promise<ReactionModel>;
});
};

View file

@ -26,14 +26,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<TeamMembershipModel>}
*/
export const transformTeamMembershipRecord = ({action, database, value}: TransformerArgs): Promise<TeamMembershipModel> => {
const raw = value.raw as TeamMembership;
const record = value.record as TeamMembershipModel;
export const transformTeamMembershipRecord = ({action, database, value}: TransformerArgs<TeamMembershipModel, TeamMembership>): Promise<TeamMembershipModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (teamMembership: TeamMembershipModel) => {
teamMembership._raw.id = isCreateAction ? (raw?.id ?? teamMembership.id) : record.id;
teamMembership._raw.id = isCreateAction ? (raw?.id ?? teamMembership.id) : record!.id;
teamMembership.teamId = raw.team_id;
teamMembership.userId = raw.user_id;
teamMembership.schemeAdmin = raw.scheme_admin;
@ -45,7 +48,7 @@ export const transformTeamMembershipRecord = ({action, database, value}: Transfo
tableName: TEAM_MEMBERSHIP,
value,
fieldsMapper,
}) as Promise<TeamMembershipModel>;
});
};
/**
@ -55,14 +58,17 @@ export const transformTeamMembershipRecord = ({action, database, value}: Transfo
* @param {RecordPair} operator.value
* @returns {Promise<TeamModel>}
*/
export const transformTeamRecord = ({action, database, value}: TransformerArgs): Promise<TeamModel> => {
const raw = value.raw as Team;
const record = value.record as TeamModel;
export const transformTeamRecord = ({action, database, value}: TransformerArgs<TeamModel, Team>): Promise<TeamModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (team: TeamModel) => {
team._raw.id = isCreateAction ? (raw?.id ?? team.id) : record.id;
team._raw.id = isCreateAction ? (raw?.id ?? team.id) : record!.id;
team.isAllowOpenInvite = raw.allow_open_invite;
team.description = raw.description;
team.displayName = raw.display_name;
@ -81,7 +87,7 @@ export const transformTeamRecord = ({action, database, value}: TransformerArgs):
tableName: TEAM,
value,
fieldsMapper,
}) as Promise<TeamModel>;
});
};
/**
@ -91,13 +97,16 @@ export const transformTeamRecord = ({action, database, value}: TransformerArgs):
* @param {RecordPair} operator.value
* @returns {Promise<TeamChannelHistoryModel>}
*/
export const transformTeamChannelHistoryRecord = ({action, database, value}: TransformerArgs): Promise<TeamChannelHistoryModel> => {
const raw = value.raw as TeamChannelHistory;
const record = value.record as TeamChannelHistoryModel;
export const transformTeamChannelHistoryRecord = ({action, database, value}: TransformerArgs<TeamChannelHistoryModel, TeamChannelHistory>): Promise<TeamChannelHistoryModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (teamChannelHistory: TeamChannelHistoryModel) => {
teamChannelHistory._raw.id = isCreateAction ? (raw.id || teamChannelHistory.id) : record.id;
teamChannelHistory._raw.id = isCreateAction ? (raw.id || teamChannelHistory.id) : record!.id;
teamChannelHistory.channelIds = raw.channel_ids;
};
@ -107,7 +116,7 @@ export const transformTeamChannelHistoryRecord = ({action, database, value}: Tra
tableName: TEAM_CHANNEL_HISTORY,
value,
fieldsMapper,
}) as Promise<TeamChannelHistoryModel>;
});
};
/**
@ -117,13 +126,16 @@ export const transformTeamChannelHistoryRecord = ({action, database, value}: Tra
* @param {RecordPair} operator.value
* @returns {Promise<TeamSearchHistoryModel>}
*/
export const transformTeamSearchHistoryRecord = ({action, database, value}: TransformerArgs): Promise<TeamSearchHistoryModel> => {
const raw = value.raw as TeamSearchHistory;
const record = value.record as TeamSearchHistoryModel;
export const transformTeamSearchHistoryRecord = ({action, database, value}: TransformerArgs<TeamSearchHistoryModel, TeamSearchHistory>): Promise<TeamSearchHistoryModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (teamSearchHistory: TeamSearchHistoryModel) => {
teamSearchHistory._raw.id = isCreateAction ? (teamSearchHistory.id) : record.id;
teamSearchHistory._raw.id = isCreateAction ? (teamSearchHistory.id) : record!.id;
teamSearchHistory.createdAt = raw.created_at;
teamSearchHistory.displayTerm = raw.display_term;
teamSearchHistory.term = raw.term;
@ -136,7 +148,7 @@ export const transformTeamSearchHistoryRecord = ({action, database, value}: Tran
tableName: TEAM_SEARCH_HISTORY,
value,
fieldsMapper,
}) as Promise<TeamSearchHistoryModel>;
});
};
/**
@ -146,13 +158,16 @@ export const transformTeamSearchHistoryRecord = ({action, database, value}: Tran
* @param {RecordPair} operator.value
* @returns {Promise<MyTeamModel>}
*/
export const transformMyTeamRecord = ({action, database, value}: TransformerArgs): Promise<MyTeamModel> => {
const raw = value.raw as MyTeam;
const record = value.record as MyTeamModel;
export const transformMyTeamRecord = ({action, database, value}: TransformerArgs<MyTeamModel, MyTeam>): Promise<MyTeamModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (myTeam: MyTeamModel) => {
myTeam._raw.id = isCreateAction ? (raw.id || myTeam.id) : record.id;
myTeam._raw.id = isCreateAction ? (raw.id || myTeam.id) : record!.id;
myTeam.roles = raw.roles;
};
@ -162,5 +177,5 @@ export const transformMyTeamRecord = ({action, database, value}: TransformerArgs
tableName: MY_TEAM,
value,
fieldsMapper,
}) as Promise<MyTeamModel>;
});
};

View file

@ -24,14 +24,17 @@ const {
* @param {RecordPair} operator.value
* @returns {Promise<ThreadModel>}
*/
export const transformThreadRecord = ({action, database, value}: TransformerArgs): Promise<ThreadModel> => {
const raw = value.raw as ThreadWithLastFetchedAt;
const record = value.record as ThreadModel;
export const transformThreadRecord = ({action, database, value}: TransformerArgs<ThreadModel, ThreadWithLastFetchedAt>): Promise<ThreadModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (thread: ThreadModel) => {
thread._raw.id = isCreateAction ? (raw?.id ?? thread.id) : record.id;
thread._raw.id = isCreateAction ? (raw?.id ?? thread.id) : record!.id;
// When post is individually fetched, we get last_reply_at as 0, so we use the record's value.
// If there is no reply at, we default to the post's create_at
@ -39,7 +42,7 @@ export const transformThreadRecord = ({action, database, value}: TransformerArgs
thread.lastViewedAt = raw.last_viewed_at ?? record?.lastViewedAt ?? 0;
thread.replyCount = raw.reply_count;
thread.isFollowing = raw.is_following ?? record?.isFollowing;
thread.isFollowing = raw.is_following ?? record?.isFollowing ?? false;
thread.unreadReplies = raw.unread_replies ?? record?.unreadReplies ?? 0;
thread.unreadMentions = raw.unread_mentions ?? record?.unreadMentions ?? 0;
thread.viewedAt = record?.viewedAt || 0;
@ -52,7 +55,7 @@ export const transformThreadRecord = ({action, database, value}: TransformerArgs
tableName: THREAD,
value,
fieldsMapper,
}) as Promise<ThreadModel>;
});
};
/**
@ -62,14 +65,17 @@ export const transformThreadRecord = ({action, database, value}: TransformerArgs
* @param {RecordPair} operator.value
* @returns {Promise<ThreadParticipantModel>}
*/
export const transformThreadParticipantRecord = ({action, database, value}: TransformerArgs): Promise<ThreadParticipantModel> => {
const raw = value.raw as ThreadParticipant;
const record = value.record as ThreadParticipantModel;
export const transformThreadParticipantRecord = ({action, database, value}: TransformerArgs<ThreadParticipantModel, ThreadParticipant>): Promise<ThreadParticipantModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
// id of participant comes from server response
const fieldsMapper = (participant: ThreadParticipantModel) => {
participant._raw.id = isCreateAction ? `${raw.thread_id}-${raw.id}` : record.id;
participant._raw.id = isCreateAction ? `${raw.thread_id}-${raw.id}` : record!.id;
participant.threadId = raw.thread_id;
participant.userId = raw.id;
};
@ -80,16 +86,19 @@ export const transformThreadParticipantRecord = ({action, database, value}: Tran
tableName: THREAD_PARTICIPANT,
value,
fieldsMapper,
}) as Promise<ThreadParticipantModel>;
});
};
export const transformThreadInTeamRecord = ({action, database, value}: TransformerArgs): Promise<ThreadInTeamModel> => {
const raw = value.raw as ThreadInTeam;
const record = value.record as ThreadInTeamModel;
export const transformThreadInTeamRecord = ({action, database, value}: TransformerArgs<ThreadInTeamModel, ThreadInTeam>): Promise<ThreadInTeamModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (threadInTeam: ThreadInTeamModel) => {
threadInTeam._raw.id = isCreateAction ? `${raw.thread_id}-${raw.team_id}` : record.id;
threadInTeam._raw.id = isCreateAction ? `${raw.thread_id}-${raw.team_id}` : record!.id;
threadInTeam.threadId = raw.thread_id;
threadInTeam.teamId = raw.team_id;
};
@ -100,18 +109,21 @@ export const transformThreadInTeamRecord = ({action, database, value}: Transform
tableName: THREADS_IN_TEAM,
value,
fieldsMapper,
}) as Promise<ThreadInTeamModel>;
});
};
export const transformTeamThreadsSyncRecord = ({action, database, value}: TransformerArgs): Promise<TeamThreadsSyncModel> => {
const raw = value.raw as TeamThreadsSync;
const record = value.record as TeamThreadsSyncModel;
export const transformTeamThreadsSyncRecord = ({action, database, value}: TransformerArgs<TeamThreadsSyncModel, TeamThreadsSync>): Promise<TeamThreadsSyncModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
if (!isCreateAction && !record) {
throw new Error('Record not found for non create action');
}
const fieldsMapper = (teamThreadsSync: TeamThreadsSyncModel) => {
teamThreadsSync._raw.id = isCreateAction ? (raw?.id ?? teamThreadsSync.id) : record.id;
teamThreadsSync.earliest = raw.earliest || record?.earliest;
teamThreadsSync.latest = raw.latest || record?.latest;
teamThreadsSync._raw.id = isCreateAction ? (raw?.id ?? teamThreadsSync.id) : record!.id;
teamThreadsSync.earliest = raw.earliest || record?.earliest || 0;
teamThreadsSync.latest = raw.latest || record?.latest || 0;
};
return prepareBaseRecord({
@ -120,5 +132,5 @@ export const transformTeamThreadsSyncRecord = ({action, database, value}: Transf
tableName: TEAM_THREADS_SYNC,
value,
fieldsMapper,
}) as Promise<TeamThreadsSyncModel>;
});
};

View file

@ -17,14 +17,14 @@ const {PREFERENCE, USER} = MM_TABLES.SERVER;
* @param {RecordPair} operator.value
* @returns {Promise<UserModel>}
*/
export const transformUserRecord = ({action, database, value}: TransformerArgs): Promise<UserModel> => {
const raw = value.raw as UserProfile;
const record = value.record as UserModel;
export const transformUserRecord = ({action, database, value}: TransformerArgs<UserModel, UserProfile>): Promise<UserModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
// id of user comes from server response
const fieldsMapper = (user: UserModel) => {
user._raw.id = isCreateAction ? (raw?.id ?? user.id) : record.id;
user._raw.id = isCreateAction ? (raw?.id ?? user.id) : record?.id || '';
user.authService = raw.auth_service;
user.deleteAt = raw.delete_at;
user.updateAt = raw.update_at;
@ -71,7 +71,7 @@ export const transformUserRecord = ({action, database, value}: TransformerArgs):
tableName: USER,
value,
fieldsMapper,
}) as Promise<UserModel>;
});
};
/**
@ -81,14 +81,14 @@ export const transformUserRecord = ({action, database, value}: TransformerArgs):
* @param {RecordPair} operator.value
* @returns {Promise<PreferenceModel>}
*/
export const transformPreferenceRecord = ({action, database, value}: TransformerArgs): Promise<PreferenceModel> => {
const raw = value.raw as PreferenceType;
const record = value.record as PreferenceModel;
export const transformPreferenceRecord = ({action, database, value}: TransformerArgs<PreferenceModel, PreferenceType>): Promise<PreferenceModel> => {
const raw = value.raw;
const record = value.record;
const isCreateAction = action === OperationType.CREATE;
// id of preference comes from server response
const fieldsMapper = (preference: PreferenceModel) => {
preference._raw.id = isCreateAction ? preference.id : record.id;
preference._raw.id = isCreateAction ? preference.id : record?.id || '';
preference.category = raw.category;
preference.name = raw.name;
preference.userId = raw.user_id;
@ -101,6 +101,6 @@ export const transformPreferenceRecord = ({action, database, value}: Transformer
tableName: PREFERENCE,
value,
fieldsMapper,
}) as Promise<PreferenceModel>;
});
};

View file

@ -5,10 +5,6 @@ import {MM_TABLES} from '@constants/database';
import type {Model} from '@nozbe/watermelondb';
import type {IdenticalRecordArgs, RangeOfValueArgs, RecordPair, RetrieveRecordsArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type TeamModel from '@typings/database/models/servers/team';
import type UserModel from '@typings/database/models/servers/user';
const {CHANNEL, POST, TEAM, USER} = MM_TABLES.SERVER;
@ -21,13 +17,12 @@ const {CHANNEL, POST, TEAM, USER} = MM_TABLES.SERVER;
* @param {Model} identicalRecord.existingRecord
* @returns {boolean}
*/
export const getValidRecordsForUpdate = ({tableName, newValue, existingRecord}: IdenticalRecordArgs) => {
export const getValidRecordsForUpdate = <T extends Model, R extends RawValue>({tableName, newValue, existingRecord}: IdenticalRecordArgs<T, R>) => {
const guardTables = [CHANNEL, POST, TEAM, USER];
if (guardTables.includes(tableName)) {
type Raw = Post | UserProfile | Team | SlashCommand | Channel;
type ExistingRecord = PostModel | UserModel | TeamModel | ChannelModel;
const shouldUpdate = (newValue as Raw).update_at === (existingRecord as ExistingRecord).updateAt;
const newValueUpdateAt = ('update_at' in newValue) ? newValue.update_at : 0;
const existingRecordUpdateAt = ('updateAt' in existingRecord) ? existingRecord.updateAt : 0;
const shouldUpdate = newValueUpdateAt === existingRecordUpdateAt;
if (shouldUpdate) {
return {
@ -66,7 +61,7 @@ export const getRangeOfValues = ({fieldName, raws}: RangeOfValueArgs) => {
* @param {any[]} raws
* @returns {{record: undefined, raw: any}[]}
*/
export const getRawRecordPairs = (raws: any[]): RecordPair[] => {
export const getRawRecordPairs = <T extends Model, R extends RawValue>(raws: R[]): Array<RecordPair<T, R>> => {
return raws.map((raw) => {
return {raw, record: undefined};
});
@ -78,7 +73,7 @@ export const getRawRecordPairs = (raws: any[]): RecordPair[] => {
* @param {RawValue[]} raws
* @param {string} key
*/
export const getUniqueRawsBy = ({raws, key}: { raws: RawValue[]; key: string}) => {
export const getUniqueRawsBy = <T extends RawValue>({raws, key}: { raws: T[]; key: string}) => {
return [...new Map(raws.map((item) => {
const curItemKey = item[key as keyof typeof item];
return [curItemKey, item];

View file

@ -28,7 +28,7 @@ export const sanitizeReactions = async ({database, post_id, rawReactions, skipSy
// similarObjects: Contains objects that are in both the RawReaction array and in the Reaction table
const similarObjects = new Set<ReactionModel>();
const createReactions: RecordPair[] = [];
const createReactions: Array<RecordPair<ReactionModel, Reaction>> = [];
const reactionsMap = reactions.reduce((result: Record<string, ReactionModel>, reaction) => {
result[`${reaction.userId}-${reaction.emojiName}`] = reaction;

View file

@ -38,7 +38,7 @@ export const sanitizeThreadParticipants = async ({database, skipSync, thread_id,
// similarObjects: Contains objects that are in both the RawParticipant array and in the ThreadParticipant table
const similarObjects = new Set<ThreadParticipantModel>();
const createParticipants: RecordPair[] = [];
const createParticipants: Array<RecordPair<ThreadParticipantModel, ThreadParticipant>> = [];
const participantsMap = participants.reduce((result: Record<string, ThreadParticipantModel>, participant) => {
result[participant.userId] = participant;
return result;

View file

@ -2,12 +2,11 @@
// See LICENSE.txt for license information.
import {renderHook} from '@testing-library/react-hooks';
import TestHelper from '@test/test_helper';
import {getUsersByUsername} from '@utils/user';
import {useMemoMentionedUser, useMemoMentionedGroup} from './markdown';
import type {GroupModel, UserModel} from '@database/models/server';
jest.mock('@utils/user', () => ({
getUsersByUsername: jest.fn(),
}));
@ -16,7 +15,7 @@ const mockGetUsersByUsername = jest.mocked(getUsersByUsername);
describe('useMemoMentionedUser', () => {
it('should return the mentioned user if found', () => {
const users = [{username: 'john_doe'} as UserModel];
const users = [TestHelper.fakeUserModel({username: 'john_doe'})];
const mentionName = 'john_doe';
const usersByUsername = {john_doe: users[0]};
mockGetUsersByUsername.mockReturnValue(usersByUsername);
@ -27,7 +26,7 @@ describe('useMemoMentionedUser', () => {
});
it('should return undefined if the mentioned user is not found', () => {
const users = [{username: 'john_doe'} as UserModel];
const users = [TestHelper.fakeUserModel({username: 'john_doe'})];
const mentionName = 'jane_doe';
const usersByUsername = {john_doe: users[0]};
mockGetUsersByUsername.mockReturnValue(usersByUsername);
@ -38,7 +37,7 @@ describe('useMemoMentionedUser', () => {
});
it('should trim trailing punctuation from the mention name', () => {
const users = [{username: 'john_doe'} as UserModel];
const users = [TestHelper.fakeUserModel({username: 'john_doe'})];
const mentionName = 'john_doe.';
const usersByUsername = {john_doe: users[0]};
mockGetUsersByUsername.mockReturnValue(usersByUsername);
@ -51,7 +50,7 @@ describe('useMemoMentionedUser', () => {
describe('useMemoMentionedGroup', () => {
it('should return the mentioned group if found', () => {
const groups = [{name: 'developers'} as GroupModel];
const groups = [TestHelper.fakeGroupModel({name: 'developers'})];
const user = undefined;
const mentionName = 'developers';
@ -61,7 +60,7 @@ describe('useMemoMentionedGroup', () => {
});
it('should return undefined if the mentioned group is not found', () => {
const groups = [{name: 'developers'} as GroupModel];
const groups = [TestHelper.fakeGroupModel({name: 'developers'})];
const user = undefined;
const mentionName = 'designers';
@ -71,8 +70,8 @@ describe('useMemoMentionedGroup', () => {
});
it('should return undefined if the user is defined', () => {
const groups = [{name: 'developers'} as GroupModel];
const user = {username: 'john_doe'} as UserModel;
const groups = [TestHelper.fakeGroupModel({name: 'developers'})];
const user = TestHelper.fakeUserModel({username: 'john_doe'});
const mentionName = 'developers';
const {result} = renderHook(() => useMemoMentionedGroup(groups, user, mentionName));
@ -81,7 +80,7 @@ describe('useMemoMentionedGroup', () => {
});
it('should trim trailing punctuation from the mention name', () => {
const groups = [{name: 'developers'} as GroupModel];
const groups = [TestHelper.fakeGroupModel({name: 'developers'})];
const user = undefined;
const mentionName = 'developers.';

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import assert from 'assert';
import {act, renderHook} from '@testing-library/react-hooks'; // Use instead of react-native version due to different behavior. Consider migrating
@ -70,13 +72,15 @@ import {
import {License} from '@constants';
import Calls from '@constants/calls';
import DatabaseManager from '@database/manager';
import {getUserById} from '@queries/servers/user';
import TestHelper from '@test/test_helper';
import type {CallJobState, LiveCaptionData} from '@mattermost/calls/lib/types';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@calls/alerts');
jest.mock('@constants/calls', () => ({
...jest.requireActual('@constants/calls'),
CALL_QUALITY_RESET_MS: 100,
}));
@ -97,12 +101,13 @@ jest.mock('@queries/servers/channel', () => ({
}));
jest.mock('@queries/servers/user', () => ({
getUserById: jest.fn(() => Promise.resolve({
username: 'user-5',
})),
getUserById: jest.fn(),
getCurrentUser: jest.fn(),
}));
const user5 = TestHelper.fakeUserModel({username: 'user-5'});
jest.mocked(getUserById).mockResolvedValue(user5);
jest.mock('react-native-navigation', () => ({
Navigation: {
pop: jest.fn(() => Promise.resolve({
@ -305,14 +310,14 @@ describe('useCallsState', () => {
},
};
const expectedChannelsWithCallsState = initialChannelsWithCallsState;
const expectedCurrentCallState = {
const expectedCurrentCallState: CurrentCall = {
...initialCurrentCallState,
...expectedCallsState['channel-1'],
} as CurrentCall;
};
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -325,11 +330,11 @@ describe('useCallsState', () => {
// test
act(() => userJoinedCall('server1', 'channel-1', 'user-3', 'session3'));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
assert.deepEqual(result.current[0].calls, expectedCallsState);
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
assert.deepEqual(result.current[2], expectedCurrentCallState);
act(() => userJoinedCall('server1', 'invalid-channel', 'user-1', 'session1'));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
assert.deepEqual(result.current[0].calls, expectedCallsState);
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
assert.deepEqual(result.current[2], expectedCurrentCallState);
});
@ -365,14 +370,14 @@ describe('useCallsState', () => {
},
};
const expectedChannelsWithCallsState = initialChannelsWithCallsState;
const expectedCurrentCallState = {
const expectedCurrentCallState: CurrentCall = {
...initialCurrentCallState,
...expectedCallsState['channel-1'],
} as CurrentCall;
};
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -385,11 +390,11 @@ describe('useCallsState', () => {
// test
act(() => userLeftCall('server1', 'channel-1', 'session1'));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
assert.deepEqual(result.current[0].calls, expectedCallsState);
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
assert.deepEqual(result.current[2], expectedCurrentCallState);
act(() => userLeftCall('server1', 'invalid-channel', 'session2'));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
assert.deepEqual(result.current[0].calls, expectedCallsState);
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
assert.deepEqual(result.current[2], expectedCurrentCallState);
});
@ -438,7 +443,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -451,7 +456,7 @@ describe('useCallsState', () => {
// test
act(() => userLeftCall('server1', 'channel-1', 'session1'));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
assert.deepEqual(result.current[0].calls, expectedCallsState);
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
assert.deepEqual(result.current[2], expectedCurrentCallState);
});
@ -469,7 +474,7 @@ describe('useCallsState', () => {
await DatabaseManager.init(['server1']);
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
assert.deepEqual(result.current[0], DefaultCallsState);
assert.deepEqual(result.current[1], {});
@ -480,7 +485,7 @@ describe('useCallsState', () => {
setCurrentCall(initialCurrentCallState);
await callStarted('server1', call1);
});
assert.deepEqual((result.current[0] as CallsState).calls, {'channel-1': call1});
assert.deepEqual(result.current[0].calls, {'channel-1': call1});
assert.deepEqual(result.current[1], {'channel-1': true});
assert.deepEqual(result.current[2], initialCurrentCallState);
expect(updateThreadFollowing).toBeCalled();
@ -533,7 +538,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -546,19 +551,19 @@ describe('useCallsState', () => {
// test
act(() => setUserMuted('server1', 'channel-1', 'session1', true));
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session1.muted, true);
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session1.muted, true);
assert.deepEqual((result.current[0]).calls['channel-1'].sessions.session1.muted, true);
assert.deepEqual((result.current[2])?.sessions.session1.muted, true);
act(() => {
setUserMuted('server1', 'channel-1', 'session1', false);
setUserMuted('server1', 'channel-1', 'session2', false);
});
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session1.muted, false);
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session2.muted, false);
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session1.muted, false);
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session2.muted, false);
assert.deepEqual((result.current[0]).calls['channel-1'].sessions.session1.muted, false);
assert.deepEqual((result.current[0]).calls['channel-1'].sessions.session2.muted, false);
assert.deepEqual((result.current[2])?.sessions.session1.muted, false);
assert.deepEqual((result.current[2])?.sessions.session2.muted, false);
act(() => setUserMuted('server1', 'channel-1', 'session2', true));
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session2.muted, true);
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session2.muted, true);
assert.deepEqual((result.current[0]).calls['channel-1'].sessions.session2.muted, true);
assert.deepEqual((result.current[2])?.sessions.session2.muted, true);
assert.deepEqual(result.current[0], initialCallsState);
act(() => setUserMuted('server1', 'invalid-channel', 'session1', true));
assert.deepEqual(result.current[0], initialCallsState);
@ -579,7 +584,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -592,9 +597,9 @@ describe('useCallsState', () => {
// test
act(() => setCallScreenOn('server1', 'channel-1', 'session1'));
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].screenOn, 'session1');
assert.deepEqual((result.current[0]).calls['channel-1'].screenOn, 'session1');
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
assert.deepEqual((result.current[2] as CurrentCall).screenOn, 'session1');
assert.deepEqual((result.current[2])?.screenOn, 'session1');
act(() => setCallScreenOff('server1', 'channel-1', 'session1'));
assert.deepEqual(result.current[0], initialCallsState);
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
@ -644,7 +649,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -655,12 +660,12 @@ describe('useCallsState', () => {
// test
act(() => setRaisedHand('server1', 'channel-1', 'session2', 345));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCalls);
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]).calls, expectedCalls);
assert.deepEqual((result.current[1]), expectedCurrentCallState);
act(() => setRaisedHand('server1', 'invalid-channel', 'session1', 345));
assert.deepEqual((result.current[0] as CallsState).calls, expectedCalls);
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]).calls, expectedCalls);
assert.deepEqual((result.current[1]), expectedCurrentCallState);
// unraise hand:
act(() => setRaisedHand('server1', 'channel-1', 'session2', 0));
@ -772,7 +777,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => setCallsState('server1', initialCallsState));
assert.deepEqual(result.current[0], initialCallsState);
@ -781,9 +786,9 @@ describe('useCallsState', () => {
// test joining a call and setting url:
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
assert.deepEqual((result.current[1] as CurrentCall | null)?.screenShareURL, '');
assert.deepEqual((result.current[1])?.screenShareURL, '');
act(() => setScreenShareURL('testUrl'));
assert.deepEqual((result.current[1] as CurrentCall | null)?.screenShareURL, 'testUrl');
assert.deepEqual((result.current[1])?.screenShareURL, 'testUrl');
act(() => {
myselfLeftCall();
@ -817,7 +822,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => setCallsState('server1', initialCallsState));
assert.deepEqual(result.current[0], initialCallsState);
@ -826,11 +831,11 @@ describe('useCallsState', () => {
// test
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, false);
assert.deepEqual((result.current[1])?.speakerphoneOn, false);
act(() => setSpeakerPhone(true));
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, true);
assert.deepEqual((result.current[1])?.speakerphoneOn, true);
act(() => setSpeakerPhone(false));
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, false);
assert.deepEqual((result.current[1])?.speakerphoneOn, false);
assert.deepEqual(result.current[0], expectedCallsState);
act(() => {
myselfLeftCall();
@ -872,7 +877,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => setCallsState('server1', initialCallsState));
assert.deepEqual(result.current[0], initialCallsState);
@ -881,10 +886,10 @@ describe('useCallsState', () => {
// test
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
assert.deepEqual((result.current[1] as CurrentCall | null)?.audioDeviceInfo, defaultAudioDeviceInfo);
assert.deepEqual((result.current[1])?.audioDeviceInfo, defaultAudioDeviceInfo);
act(() => setAudioDeviceInfo(newAudioDeviceInfo));
assert.deepEqual((result.current[1] as CurrentCall | null)?.audioDeviceInfo, newAudioDeviceInfo);
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, false);
assert.deepEqual((result.current[1])?.audioDeviceInfo, newAudioDeviceInfo);
assert.deepEqual((result.current[1])?.speakerphoneOn, false);
assert.deepEqual(result.current[0], expectedCallsState);
act(() => {
myselfLeftCall();
@ -1025,7 +1030,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => setCallsState('server1', initialCallsState));
assert.deepEqual(result.current[0], initialCallsState);
@ -1041,8 +1046,8 @@ describe('useCallsState', () => {
// call quality goes bad
act(() => processMeanOpinionScore(3.4999));
assert.deepEqual((result.current[1] as CurrentCall).callQualityAlert, true);
assert.equal((result.current[1] as CurrentCall).callQualityAlertDismissed, 0);
assert.deepEqual((result.current[1])?.callQualityAlert, true);
assert.equal((result.current[1])?.callQualityAlertDismissed, 0);
// call quality goes good
act(() => processMeanOpinionScore(4));
@ -1050,26 +1055,26 @@ describe('useCallsState', () => {
// call quality goes bad
act(() => processMeanOpinionScore(3.499));
assert.deepEqual((result.current[1] as CurrentCall).callQualityAlert, true);
assert.equal((result.current[1] as CurrentCall).callQualityAlertDismissed, 0);
assert.deepEqual((result.current[1])?.callQualityAlert, true);
assert.equal((result.current[1])?.callQualityAlertDismissed, 0);
// dismiss call quality alert
const timeNow = Date.now();
act(() => setCallQualityAlertDismissed());
assert.deepEqual((result.current[1] as CurrentCall).callQualityAlert, false);
assert.equal((result.current[1] as CurrentCall).callQualityAlertDismissed >= timeNow &&
(result.current[1] as CurrentCall).callQualityAlertDismissed <= Date.now(), true);
assert.deepEqual((result.current[1])?.callQualityAlert, false);
assert.equal((result.current[1]!).callQualityAlertDismissed >= timeNow &&
(result.current[1]!).callQualityAlertDismissed <= Date.now(), true);
// call quality goes bad, but we're not past the dismissed limit
act(() => processMeanOpinionScore(3.4999));
assert.deepEqual((result.current[1] as CurrentCall).callQualityAlert, false);
assert.deepEqual((result.current[1])?.callQualityAlert, false);
// test that the dismiss expired
await act(async () => {
await new Promise((r) => setTimeout(r, 101));
processMeanOpinionScore(3.499);
});
assert.deepEqual((result.current[1] as CurrentCall).callQualityAlert, true);
assert.deepEqual((result.current[1])?.callQualityAlert, true);
});
it('voiceOn and Off', async () => {
@ -1265,7 +1270,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -1276,11 +1281,11 @@ describe('useCallsState', () => {
// test
act(() => setRecordingState('server1', 'channel-1', recState));
assert.deepEqual((result.current[0] as CallsState), expectedCallsState);
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]), expectedCallsState);
assert.deepEqual((result.current[1]), expectedCurrentCallState);
act(() => setRecordingState('server1', 'channel-2', recState));
assert.deepEqual((result.current[0] as CallsState).calls['channel-2'], {...call2, recState});
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]).calls['channel-2'], {...call2, recState});
assert.deepEqual((result.current[1]), expectedCurrentCallState);
act(() => setRecordingState('server1', 'channel-1', {...recState, start_at: recState.start_at + 1}));
expect(needsRecordingAlert).toBeCalled();
});
@ -1318,7 +1323,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -1329,11 +1334,11 @@ describe('useCallsState', () => {
// test
act(() => setHost('server1', 'channel-1', 'user-52'));
assert.deepEqual((result.current[0] as CallsState), expectedCallsState);
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]), expectedCallsState);
assert.deepEqual((result.current[1]), expectedCurrentCallState);
act(() => setHost('server1', 'channel-2', 'user-1923'));
assert.deepEqual((result.current[0] as CallsState).calls['channel-2'], {...call2, hostId: 'user-1923'});
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
assert.deepEqual((result.current[0]).calls['channel-2'], {...call2, hostId: 'user-1923'});
assert.deepEqual((result.current[1]), expectedCurrentCallState);
act(() => setHost('server1', 'channel-1', 'myUserId'));
expect(needsRecordingAlert).toBeCalled();
});
@ -1352,7 +1357,7 @@ describe('useCallsState', () => {
incomingCalls: [{
callID: 'callDM',
callerID: 'user-5',
callerModel: {username: 'user-5'} as UserModel,
callerModel: user5,
channelID: 'channel-private',
myUserId: 'myId',
serverUrl: 'server1',
@ -1452,7 +1457,7 @@ describe('useCallsState', () => {
incomingCalls: [{
callID: 'callDM',
callerID: 'user-5',
callerModel: {username: 'user-5'} as UserModel,
callerModel: TestHelper.fakeUserModel({username: 'user-5'}),
channelID: 'channel-private',
myUserId: 'myId',
serverUrl: 'server1',
@ -1576,7 +1581,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()];
return [useCallsState('server1'), useChannelsWithCalls('server1'), useCurrentCall()] as const;
});
await act(async () => {
await setCallsState('server1', initialCallsState);
@ -1599,7 +1604,7 @@ describe('useCallsState', () => {
// test: update existing call
const updatedCall1 = {...call1, screenOn: 'newScreen'};
await act(async () => setCallForChannel('server1', 'channel-1', updatedCall1));
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'], updatedCall1);
assert.deepEqual(result.current[0].calls['channel-1'], updatedCall1);
assert.deepEqual(result.current[2], {...initialCurrentCallState, screenOn: 'newScreen'});
// test: remove call
@ -1613,7 +1618,7 @@ describe('useCallsState', () => {
// test: just update enabled state
await act(async () => setCallForChannel('server1', 'channel-3', undefined, true));
assert.deepEqual((result.current[0] as CallsState).enabled['channel-3'], true);
assert.deepEqual(result.current[0].enabled['channel-3'], true);
assert.deepEqual(result.current[1], {'channel-2': true});
});
@ -1663,7 +1668,7 @@ describe('useCallsState', () => {
// setup
const {result} = renderHook(() => {
return [useCallsState('server1'), useCurrentCall()];
return [useCallsState('server1'), useCurrentCall()] as const;
});
act(() => {
setCallsState('server1', initialCallsState);
@ -1679,9 +1684,9 @@ describe('useCallsState', () => {
receivedCaption('server1', caption1user2Data);
});
assert.deepEqual(result.current[0], initialCallsState);
let currentCall = result.current[1] as CurrentCall;
assert.equal(currentCall.captions.session1.text, 'caption 2');
assert.equal(currentCall.captions.session2.text, 'caption 1 user 2');
let currentCall = result.current[1];
assert.equal(currentCall?.captions.session1.text, 'caption 2');
assert.equal(currentCall?.captions.session2.text, 'caption 1 user 2');
// test sending the next captions for users 1 and 2
act(() => {
@ -1689,8 +1694,8 @@ describe('useCallsState', () => {
receivedCaption('server1', caption2user2Data);
});
assert.deepEqual(result.current[0], initialCallsState);
currentCall = result.current[1] as CurrentCall;
assert.equal(currentCall.captions.session1.text, 'caption 3');
assert.equal(currentCall.captions.session2.text, 'caption 2 user 2');
currentCall = result.current[1];
assert.equal(currentCall?.captions.session1.text, 'caption 3');
assert.equal(currentCall?.captions.session2.text, 'caption 2 user 2');
});
});

View file

@ -7,8 +7,9 @@ import {Alert} from 'react-native';
import {SelectedTrackType} from 'react-native-video';
import {type CallsConfigState, DefaultCallsConfig} from '@calls/types/calls';
import {License, Post} from '@constants';
import {License, Post, Preferences} from '@constants';
import {NOTIFICATION_SUB_TYPE} from '@constants/push_notification';
import TestHelper from '@test/test_helper';
import {
getICEServersConfigs,
@ -28,7 +29,6 @@ import {
getTranscriptionUri,
} from './utils';
import type UserModel from '@typings/database/models/servers/user';
import type {IntlShape} from 'react-intl';
describe('getICEServersConfigs', () => {
@ -136,21 +136,21 @@ describe('sortSessions', () => {
userId: 'user1',
muted: true,
raisedHand: 0,
userModel: {username: 'charlie'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'charlie'}),
},
2: {
sessionId: '2',
userId: 'user2',
muted: true,
raisedHand: 0,
userModel: {username: 'alice'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'alice'}),
},
3: {
sessionId: '3',
userId: 'user3',
muted: true,
raisedHand: 0,
userModel: {username: 'bob'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'bob'}),
},
};
@ -165,21 +165,21 @@ describe('sortSessions', () => {
userId: 'user1',
muted: true,
raisedHand: 0,
userModel: {username: 'a'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'a'}),
},
2: {
sessionId: '2',
userId: 'user2',
muted: false,
raisedHand: 0,
userModel: {username: 'b'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'b'}),
},
3: {
sessionId: '3',
userId: 'user3',
muted: true,
raisedHand: 1000,
userModel: {username: 'c'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'c'}),
},
};
@ -194,14 +194,14 @@ describe('sortSessions', () => {
userId: 'user1',
muted: true,
raisedHand: 2000, // Raised hand second
userModel: {username: 'a'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'a'}),
},
2: {
sessionId: '2',
userId: 'user2',
muted: true,
raisedHand: 1000, // Raised hand first
userModel: {username: 'b'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'b'}),
},
};
@ -252,14 +252,14 @@ describe('getHandsRaisedNames', () => {
sessionId: '1',
userId: 'user1',
raisedHand: 2000,
userModel: {username: 'alice'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'alice'}),
muted: false,
},
{
sessionId: '2',
userId: 'user2',
raisedHand: 1000,
userModel: {username: 'bob'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'bob'}),
muted: false,
},
];
@ -274,7 +274,7 @@ describe('getHandsRaisedNames', () => {
sessionId: '1',
userId: 'user1',
raisedHand: 1000,
userModel: {username: 'alice'} as UserModel,
userModel: TestHelper.fakeUserModel({username: 'alice'}),
muted: false,
},
];
@ -318,9 +318,8 @@ describe('isHostControlsAllowed and areGroupCallsAllowed', () => {
describe('isCallsCustomMessage', () => {
it('identifies calls messages', () => {
expect(isCallsCustomMessage({type: Post.POST_TYPES.CUSTOM_CALLS} as Post)).toBe(true);
expect(isCallsCustomMessage({type: 'regular_post' as unknown} as Post)).toBe(false);
expect(isCallsCustomMessage({} as Post)).toBe(false);
expect(isCallsCustomMessage(TestHelper.fakePost({type: Post.POST_TYPES.CUSTOM_CALLS}))).toBe(true);
expect(isCallsCustomMessage(TestHelper.fakePost({type: ''}))).toBe(false); // Regular post
});
});
@ -358,8 +357,9 @@ describe('errorAlert', () => {
describe('makeCallsTheme', () => {
it('creates calls theme from base theme', () => {
const theme = {
...Preferences.THEMES.denim,
sidebarBg: '#000000',
} as Theme;
};
const callsTheme = makeCallsTheme(theme);
@ -415,7 +415,7 @@ describe('getTranscriptionUri', () => {
describe('getCallPropsFromPost', () => {
test('undefined props', () => {
const post = {} as Post;
const post = TestHelper.fakePost({props: undefined});
const props = getCallPropsFromPost(post);
@ -429,9 +429,7 @@ describe('getCallPropsFromPost', () => {
});
test('missing props', () => {
const post = {
props: {},
} as Post;
const post = TestHelper.fakePost({props: {}});
const props = getCallPropsFromPost(post);
@ -455,9 +453,9 @@ describe('getCallPropsFromPost', () => {
recording_files: 45,
};
const post = {
props: callProps as unknown,
} as Post;
const post = TestHelper.fakePost({
props: callProps,
});
const props = getCallPropsFromPost(post);
@ -503,9 +501,9 @@ describe('getCallPropsFromPost', () => {
participants: ['userA', 'userB'],
};
const post = {
props: callProps as unknown,
} as Post;
const post = TestHelper.fakePost({
props: callProps,
});
const props = getCallPropsFromPost(post);

View file

@ -3,7 +3,7 @@
/* eslint-disable max-lines */
import {Database, Model, Query, Relation, Q} from '@nozbe/watermelondb';
import {Database, Q} from '@nozbe/watermelondb';
import {of as of$} from 'rxjs';
import {General, Permissions} from '@constants';
@ -18,7 +18,6 @@ import {prepareChannels,
prepareMyChannelsForTeam,
prepareDeleteChannel,
prepareDeleteBookmarks,
type ChannelMembershipsExtended,
queryAllChannels,
queryAllChannelsForTeam,
queryAllChannelsInfo,
@ -71,13 +70,10 @@ import {queryRoles} from './role';
import {getCurrentChannelId, observeCurrentChannelId, observeCurrentUserId} from './system';
import {observeTeammateNameDisplay} from './user';
import type {UserModel} from '@database/models/server';
import type ChannelModel from '@typings/database/models/servers/channel';
import type ChannelBookmarkModel from '@typings/database/models/servers/channel_bookmark';
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
jest.mock('./role');
jest.mock('./system');
@ -98,10 +94,22 @@ describe('prepareChannels', () => {
});
it('should prepare channels, channelInfos, channelMemberships, memberships, and myChannelSettings', () => {
const channels = [{id: 'channel1'}, {id: 'channel2'}] as Channel[];
const channelInfos = [{id: 'channel1'}, {id: 'channel2'}] as ChannelInfo[];
const channelMemberships = [{channel_id: 'channel1', user_id: 'user1'}, {channel_id: 'channel2', user_id: 'user2'}] as ChannelMembershipsExtended[];
const memberships = [{channel_id: 'channel1', user_id: 'user1'}, {channel_id: 'channel2', user_id: 'user2'}] as ChannelMembership[];
const channels = [
TestHelper.fakeChannel({id: 'channel1'}),
TestHelper.fakeChannel({id: 'channel2'}),
];
const channelInfos = [
TestHelper.fakeChannelInfo({id: 'channel1'}),
TestHelper.fakeChannelInfo({id: 'channel2'}),
];
const channelMemberships = [
TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}),
TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}),
];
const memberships = [
TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}),
TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}),
];
const channelRecords = Promise.resolve([]);
const channelInfoRecords = Promise.resolve([]);
@ -109,11 +117,11 @@ describe('prepareChannels', () => {
const myChannelRecords = Promise.resolve([]);
const myChannelSettingsRecords = Promise.resolve([]);
(operator.handleChannel as jest.Mock).mockReturnValue(channelRecords);
(operator.handleChannelInfo as jest.Mock).mockReturnValue(channelInfoRecords);
(operator.handleChannelMembership as jest.Mock).mockReturnValue(membershipRecords);
(operator.handleMyChannel as jest.Mock).mockReturnValue(myChannelRecords);
(operator.handleMyChannelSettings as jest.Mock).mockReturnValue(myChannelSettingsRecords);
jest.mocked(operator.handleChannel).mockReturnValue(channelRecords);
jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords);
jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords);
jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords);
jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords);
const result = prepareChannels(operator, channels, channelInfos, channelMemberships, memberships, true);
@ -126,7 +134,7 @@ describe('prepareChannels', () => {
});
it('should return an empty array if an error occurs', () => {
(operator.handleChannel as jest.Mock).mockImplementation(() => {
jest.mocked(operator.handleChannel).mockImplementation(() => {
throw new Error('Test error');
});
@ -151,13 +159,13 @@ describe('prepareMissingChannelsForAllTeams', () => {
it('should prepare missing channels for all teams', () => {
const channels = [
{id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456},
{id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112},
] as Channel[];
TestHelper.fakeChannel({id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456}),
TestHelper.fakeChannel({id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112}),
];
const channelMembers = [
{channel_id: 'channel1', user_id: 'user1'},
{channel_id: 'channel2', user_id: 'user2'},
] as ChannelMembership[];
TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}),
TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}),
];
const channelRecords = Promise.resolve([]);
const channelInfoRecords = Promise.resolve([]);
@ -165,11 +173,11 @@ describe('prepareMissingChannelsForAllTeams', () => {
const myChannelRecords = Promise.resolve([]);
const myChannelSettingsRecords = Promise.resolve([]);
(operator.handleChannel as jest.Mock).mockReturnValue(channelRecords);
(operator.handleChannelInfo as jest.Mock).mockReturnValue(channelInfoRecords);
(operator.handleChannelMembership as jest.Mock).mockReturnValue(membershipRecords);
(operator.handleMyChannel as jest.Mock).mockReturnValue(myChannelRecords);
(operator.handleMyChannelSettings as jest.Mock).mockReturnValue(myChannelSettingsRecords);
jest.mocked(operator.handleChannel).mockReturnValue(channelRecords);
jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords);
jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords);
jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords);
jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords);
const result = prepareMissingChannelsForAllTeams(operator, channels, channelMembers, true);
@ -184,31 +192,31 @@ describe('prepareMissingChannelsForAllTeams', () => {
});
expect(operator.handleChannelMembership).toHaveBeenCalledWith({
channelMemberships: [
{channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456},
{channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}),
],
prepareRecordsOnly: true,
});
expect(operator.handleMyChannel).toHaveBeenCalledWith({
channels,
myChannels: [
{channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456},
{channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}),
],
prepareRecordsOnly: true,
isCRTEnabled: true,
});
expect(operator.handleMyChannelSettings).toHaveBeenCalledWith({
settings: [
{channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456},
{channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}),
],
prepareRecordsOnly: true,
});
});
it('should return an empty array if an error occurs', () => {
(operator.handleChannel as jest.Mock).mockImplementation(() => {
jest.mocked(operator.handleChannel).mockImplementation(() => {
throw new Error('Test error');
});
@ -245,13 +253,13 @@ describe('prepareMyChannelsForTeam', () => {
it('should prepare my channels for a team', async () => {
const teamId = 'team_id';
const channels = [
{id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456},
{id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112},
] as Channel[];
TestHelper.fakeChannel({id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456}),
TestHelper.fakeChannel({id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112}),
];
const channelMembers = [
{channel_id: 'channel1', user_id: 'user1'},
{channel_id: 'channel2', user_id: 'user2'},
] as ChannelMembership[];
TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}),
TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}),
];
const allChannelsForTeam = {
channel1: {id: 'channel1'},
@ -274,11 +282,11 @@ describe('prepareMyChannelsForTeam', () => {
const myChannelRecords = Promise.resolve([]);
const myChannelSettingsRecords = Promise.resolve([]);
(operator.handleChannel as jest.Mock).mockReturnValue(channelRecords);
(operator.handleChannelInfo as jest.Mock).mockReturnValue(channelInfoRecords);
(operator.handleChannelMembership as jest.Mock).mockReturnValue(membershipRecords);
(operator.handleMyChannel as jest.Mock).mockReturnValue(myChannelRecords);
(operator.handleMyChannelSettings as jest.Mock).mockReturnValue(myChannelSettingsRecords);
jest.mocked(operator.handleChannel).mockReturnValue(channelRecords);
jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords);
jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords);
jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords);
jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords);
query = jest.fn().mockResolvedValueOnce(Object.entries(allChannelsForTeam).map((c) => c[1])).mockResolvedValueOnce(Object.entries(allChannelsInfoForTeam).map((i) => i[1]));
const result = await prepareMyChannelsForTeam(operator, teamId, channels, channelMembers, true);
@ -288,31 +296,31 @@ describe('prepareMyChannelsForTeam', () => {
expect(operator.handleChannel).toHaveBeenCalledWith({channels, prepareRecordsOnly: true});
expect(operator.handleChannelInfo).toHaveBeenCalledWith({
channelInfos: [
{id: 'channel1', header: 'header1', purpose: 'purpose1', guest_count: 2, member_count: 10, pinned_post_count: 1, files_count: 5},
{id: 'channel2', header: 'header2', purpose: 'purpose2', guest_count: 3, member_count: 20, pinned_post_count: 2, files_count: 10},
expect.objectContaining({id: 'channel1', header: 'header1', purpose: 'purpose1', guest_count: 2, member_count: 10, pinned_post_count: 1, files_count: 5}),
expect.objectContaining({id: 'channel2', header: 'header2', purpose: 'purpose2', guest_count: 3, member_count: 20, pinned_post_count: 2, files_count: 10}),
],
prepareRecordsOnly: true,
});
expect(operator.handleChannelMembership).toHaveBeenCalledWith({
channelMemberships: [
{channel_id: 'channel1', user_id: 'user1'},
{channel_id: 'channel2', user_id: 'user2'},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1'}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2'}),
],
prepareRecordsOnly: true,
});
expect(operator.handleMyChannel).toHaveBeenCalledWith({
channels,
myChannels: [
{channel_id: 'channel1', user_id: 'user1', id: 'channel1'},
{channel_id: 'channel2', user_id: 'user2', id: 'channel2'},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1'}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2'}),
],
prepareRecordsOnly: true,
isCRTEnabled: true,
});
expect(operator.handleMyChannelSettings).toHaveBeenCalledWith({
settings: [
{channel_id: 'channel1', user_id: 'user1', id: 'channel1'},
{channel_id: 'channel2', user_id: 'user2', id: 'channel2'},
expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1'}),
expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2'}),
],
prepareRecordsOnly: true,
});
@ -323,38 +331,35 @@ describe('prepareDeleteChannel', () => {
let channel: ChannelModel;
beforeEach(() => {
channel = {
channel = TestHelper.fakeChannelModel({
prepareDestroyPermanently: jest.fn().mockReturnValue({}),
membership: {fetch: jest.fn()} as unknown as Relation<Model>,
info: {fetch: jest.fn()} as unknown as Relation<Model>,
categoryChannel: {fetch: jest.fn()} as unknown as Relation<Model>,
members: {fetch: jest.fn()} as unknown as Query<Model>,
drafts: {fetch: jest.fn()} as unknown as Query<Model>,
postsInChannel: {fetch: jest.fn()} as unknown as Query<Model>,
posts: {fetch: jest.fn()} as unknown as Query<Model>,
bookmarks: {fetch: jest.fn()} as unknown as Query<Model>,
} as unknown as ChannelModel;
});
});
it('should prepare models for deletion', async () => {
const prepareDestroyPermanently = jest.fn().mockReturnValue({});
const membershipModel = {prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'membership'})};
const infoModel = {prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'info'})};
const categoryChannelModel = {prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'category'})};
const memberModels = [{prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'member'})}];
const draftModels = [{prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'draft'})}];
const postsInChannelModels = [{prepareDestroyPermanently}];
const postModels = [{id: 'post1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'post'})}];
const bookmarkModels = [{id: 'bookmark1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'bookmark'})}];
const membershipModel = TestHelper.fakeMyChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'membership'})});
const infoModel = TestHelper.fakeChannelInfoModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'info'})});
const categoryChannelModel = TestHelper.fakeCategoryChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'category'})});
const memberModels = [TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'member'})})];
const draftModels = [TestHelper.fakeDraftModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'draft'})})];
const postsInChannelModels = [
TestHelper.fakePostsInChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'postsInChannel'})}),
];
const postModels = [
TestHelper.fakePostModel({id: 'post1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'post'})}),
];
const bookmarkModels = [
TestHelper.fakeChannelBookmarkModel({id: 'bookmark1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'bookmark'})}),
];
(channel.membership.fetch as jest.Mock).mockResolvedValue(membershipModel);
(channel.info.fetch as jest.Mock).mockResolvedValue(infoModel);
(channel.categoryChannel.fetch as jest.Mock).mockResolvedValue(categoryChannelModel);
(channel.members.fetch as jest.Mock).mockResolvedValue(memberModels);
(channel.drafts.fetch as jest.Mock).mockResolvedValue(draftModels);
(channel.postsInChannel.fetch as jest.Mock).mockResolvedValue(postsInChannelModels);
(channel.posts.fetch as jest.Mock).mockResolvedValue(postModels);
(channel.bookmarks.fetch as jest.Mock).mockResolvedValue(bookmarkModels);
jest.mocked(channel.membership.fetch).mockResolvedValue(membershipModel);
jest.mocked(channel.info.fetch).mockResolvedValue(infoModel);
jest.mocked(channel.categoryChannel.fetch).mockResolvedValue(categoryChannelModel);
jest.mocked(channel.members.fetch).mockResolvedValue(memberModels);
jest.mocked(channel.drafts.fetch).mockResolvedValue(draftModels);
jest.mocked(channel.postsInChannel.fetch).mockResolvedValue(postsInChannelModels);
jest.mocked(channel.posts.fetch).mockResolvedValue(postModels);
jest.mocked(channel.bookmarks.fetch).mockResolvedValue(bookmarkModels);
const result = await prepareDeleteChannel(channel);
@ -365,7 +370,7 @@ describe('prepareDeleteChannel', () => {
{id: 'category'},
{id: 'member'},
{id: 'draft'},
{},
{id: 'postsInChannel'},
{id: 'post'},
{id: 'bookmark'},
]);
@ -381,9 +386,9 @@ describe('prepareDeleteChannel', () => {
});
it('should handle errors gracefully', async () => {
(channel.membership.fetch as jest.Mock).mockRejectedValue(new Error('Test error'));
(channel.info.fetch as jest.Mock).mockRejectedValue(new Error('Test error'));
(channel.categoryChannel.fetch as jest.Mock).mockRejectedValue(new Error('Test error'));
jest.mocked(channel.membership.fetch).mockRejectedValue(new Error('Test error'));
jest.mocked(channel.info.fetch).mockRejectedValue(new Error('Test error'));
jest.mocked(channel.categoryChannel.fetch).mockRejectedValue(new Error('Test error'));
const result = await prepareDeleteChannel(channel);
@ -396,17 +401,16 @@ describe('prepareDeleteBookmarks', () => {
let bookmark: ChannelBookmarkModel;
beforeEach(() => {
bookmark = {
bookmark = TestHelper.fakeChannelBookmarkModel({
prepareDestroyPermanently: jest.fn().mockReturnValue({}),
file: {fetch: jest.fn()} as unknown as Relation<Model>,
fileId: 'file_id',
} as unknown as ChannelBookmarkModel;
});
});
it('should prepare bookmark and associated file for deletion', async () => {
const fileModel = {prepareDestroyPermanently: jest.fn().mockReturnValue({})};
const fileModel = TestHelper.fakeFileModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})});
(bookmark.file.fetch as jest.Mock).mockResolvedValue(fileModel);
jest.mocked(bookmark.file.fetch).mockResolvedValue(fileModel);
const result = await prepareDeleteBookmarks(bookmark);
@ -417,7 +421,7 @@ describe('prepareDeleteBookmarks', () => {
});
it('should handle errors gracefully when fetching associated file', async () => {
(bookmark.file.fetch as jest.Mock).mockRejectedValue(new Error('Test error'));
jest.mocked(bookmark.file.fetch).mockRejectedValue(new Error('Test error'));
const result = await prepareDeleteBookmarks(bookmark);
@ -692,10 +696,10 @@ describe('Channel Functions', () => {
describe('getMyChannel', () => {
it('should return the channel member if found', async () => {
const channelId = 'channel_id';
const mockMember = {id: channelId} as MyChannelModel;
(database.get as jest.Mock).mockReturnValue({
const mockMember = TestHelper.fakeMyChannelModel({id: channelId});
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockMember),
});
} as any);
const result = await getMyChannel(database, channelId);
@ -705,9 +709,9 @@ describe('Channel Functions', () => {
it('should return undefined if the channel member is not found', async () => {
const channelId = 'channel_id';
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockRejectedValue(new Error('Not found')),
});
} as any);
const result = await getMyChannel(database, channelId);
@ -722,9 +726,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([{id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))}])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeMyChannel(database, channelId);
@ -740,9 +744,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeMyChannel(database, channelId);
@ -761,9 +765,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([{id: channelId, roles, observe: jest.fn().mockReturnValue(of$({roles}))}])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeMyChannelRoles(database, channelId);
@ -777,9 +781,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeMyChannelRoles(database, channelId);
@ -792,10 +796,10 @@ describe('Channel Functions', () => {
describe('getChannelById', () => {
it('should return the channel if found', async () => {
const channelId = 'channel_id';
const mockedChannel = {id: channelId} as ChannelModel;
(database.get as jest.Mock).mockReturnValue({
const mockedChannel = TestHelper.fakeChannelModel({id: channelId});
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockedChannel),
});
} as any);
const result = await getChannelById(database, channelId);
@ -805,9 +809,9 @@ describe('Channel Functions', () => {
it('should return undefined if the channel is not found', async () => {
const channelId = 'channel_id';
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockRejectedValue(new Error('Not found')),
});
} as any);
const result = await getChannelById(database, channelId);
@ -822,9 +826,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([{id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))}])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeChannel(database, channelId);
@ -840,9 +844,9 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
} as any);
const result = observeChannel(database, channelId);
@ -858,12 +862,12 @@ describe('Channel Functions', () => {
it('should return the channel if found', async () => {
const teamId = 'team_id';
const channelName = 'channel_name';
const mockedChannel = {id: 'channel_id', name: channelName} as ChannelModel;
(database.get as jest.Mock).mockReturnValue({
const mockedChannel = TestHelper.fakeChannelModel({id: 'channel_id', name: channelName});
jest.mocked(database.get).mockReturnValue({
query: jest.fn().mockReturnValue({
fetch: jest.fn().mockResolvedValue([mockedChannel]),
}),
});
} as any);
const result = await getChannelByName(database, teamId, channelName);
@ -874,11 +878,11 @@ describe('Channel Functions', () => {
it('should return undefined if the channel is not found', async () => {
const teamId = 'team_id';
const channelName = 'channel_name';
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: jest.fn().mockReturnValue({
fetch: jest.fn().mockResolvedValue([]),
}),
});
} as any);
const result = await getChannelByName(database, teamId, channelName);
@ -890,10 +894,10 @@ describe('Channel Functions', () => {
describe('getChannelInfo', () => {
it('should return the channel info if found', async () => {
const channelId = 'channel_id';
const mockChannelInfo = {id: channelId} as ChannelInfoModel;
(database.get as jest.Mock).mockReturnValue({
const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: channelId});
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockChannelInfo),
});
} as any);
const result = await getChannelInfo(database, channelId);
@ -903,9 +907,9 @@ describe('Channel Functions', () => {
it('should return undefined if the channel info is not found', async () => {
const channelId = 'channel_id';
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockRejectedValue(new Error('Not found')),
});
} as any);
const result = await getChannelInfo(database, channelId);
@ -927,12 +931,12 @@ describe('Channel Functions', () => {
it('should return the default channel for the team', async () => {
const teamId = 'team_id';
const defaultChannel = {id: 'default_channel', name: General.DEFAULT_CHANNEL} as ChannelModel;
const myFirstTeamChannel = {id: 'first_team_channel'} as ChannelModel;
const roles = [{permissions: [Permissions.JOIN_PUBLIC_CHANNELS]}];
(queryRoles as jest.Mock).mockReturnValue({fetch: jest.fn().mockResolvedValue(roles)});
(hasPermission as jest.Mock).mockReturnValue(true);
(mockQuery as jest.Mock).mockReturnValue({
const defaultChannel = TestHelper.fakeChannelModel({id: 'default_channel', name: General.DEFAULT_CHANNEL});
const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'});
const roles = [TestHelper.fakeRoleModel({permissions: [Permissions.JOIN_PUBLIC_CHANNELS]})];
jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles));
jest.mocked(hasPermission).mockReturnValue(true);
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue([defaultChannel, myFirstTeamChannel]),
});
@ -955,11 +959,11 @@ describe('Channel Functions', () => {
it('should return the default channel for the team with ignore id', async () => {
const teamId = 'team_id';
const defaultChannel = {id: 'default_channel', name: General.DEFAULT_CHANNEL} as ChannelModel;
const myFirstTeamChannel = {id: 'first_team_channel'} as ChannelModel;
(queryRoles as jest.Mock).mockReturnValue({fetch: jest.fn().mockResolvedValue([])});
(hasPermission as jest.Mock).mockReturnValue(true);
(mockQuery as jest.Mock).mockReturnValue({
const defaultChannel = TestHelper.fakeChannelModel({id: 'default_channel', name: General.DEFAULT_CHANNEL});
const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'});
jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery([]));
jest.mocked(hasPermission).mockReturnValue(true);
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue([defaultChannel, myFirstTeamChannel]),
});
@ -982,11 +986,11 @@ describe('Channel Functions', () => {
it('should return the first team channel if no default channel is found', async () => {
const teamId = 'team_id';
const myFirstTeamChannel = {id: 'first_team_channel'} as ChannelModel;
const roles = [{permissions: []}];
(queryRoles as jest.Mock).mockReturnValue({fetch: jest.fn().mockResolvedValue(roles)});
(hasPermission as jest.Mock).mockReturnValue(false);
(mockQuery as jest.Mock).mockReturnValue({
const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'});
const roles = [TestHelper.fakeRoleModel({permissions: []})];
jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles));
jest.mocked(hasPermission).mockReturnValue(false);
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue([myFirstTeamChannel]),
});
@ -1009,10 +1013,10 @@ describe('Channel Functions', () => {
it('should return undefined if no channels are found', async () => {
const teamId = 'team_id';
const roles = [{permissions: [Permissions.JOIN_PUBLIC_CHANNELS]}];
(queryRoles as jest.Mock).mockReturnValue({fetch: jest.fn().mockResolvedValue(roles)});
(hasPermission as jest.Mock).mockReturnValue(true);
(mockQuery as jest.Mock).mockReturnValue({
const roles = [TestHelper.fakeRoleModel({permissions: [Permissions.JOIN_PUBLIC_CHANNELS]})];
jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles));
jest.mocked(hasPermission).mockReturnValue(true);
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue([]),
});
@ -1037,11 +1041,11 @@ describe('Channel Functions', () => {
describe('getCurrentChannel', () => {
it('should return the current channel if found', async () => {
const currentChannelId = 'current_channel_id';
const mockedChannel = {id: currentChannelId} as ChannelModel;
(getCurrentChannelId as jest.Mock).mockResolvedValue(currentChannelId);
(database.get as jest.Mock).mockReturnValue({
const mockedChannel = TestHelper.fakeChannelModel({id: currentChannelId});
jest.mocked(getCurrentChannelId).mockResolvedValue(currentChannelId);
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockedChannel),
});
} as any);
const result = await getCurrentChannel(database);
@ -1051,7 +1055,7 @@ describe('Channel Functions', () => {
});
it('should return undefined if no current channel is found', async () => {
(getCurrentChannelId as jest.Mock).mockResolvedValue(undefined);
jest.mocked(getCurrentChannelId).mockResolvedValue('');
const result = await getCurrentChannel(database);
@ -1063,11 +1067,11 @@ describe('Channel Functions', () => {
describe('getCurrentChannelInfo', () => {
it('should return the current channel info if found', async () => {
const currentChannelId = 'current_channel_id';
const mockChannelInfo = {id: currentChannelId} as ChannelInfoModel;
(getCurrentChannelId as jest.Mock).mockResolvedValue(currentChannelId);
(database.get as jest.Mock).mockReturnValue({
const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: currentChannelId});
jest.mocked(getCurrentChannelId).mockResolvedValue(currentChannelId);
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockChannelInfo),
});
} as any);
const result = await getCurrentChannelInfo(database);
@ -1077,7 +1081,7 @@ describe('Channel Functions', () => {
});
it('should return undefined if no current channel info is found', async () => {
(getCurrentChannelId as jest.Mock).mockResolvedValue(undefined);
jest.mocked(getCurrentChannelId).mockResolvedValue('');
const result = await getCurrentChannelInfo(database);
@ -1089,14 +1093,14 @@ describe('Channel Functions', () => {
describe('observeCurrentChannel', () => {
it('should observe the current channel', () => {
const currentChannelId = 'current_channel_id';
const mockedChannel = {id: currentChannelId, observe: jest.fn().mockReturnValue(of$({id: currentChannelId}))} as unknown as ChannelModel;
const mockedChannel = TestHelper.fakeChannelModel({id: currentChannelId, observe: jest.fn().mockReturnValue(of$({id: currentChannelId}))});
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([mockedChannel])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
(observeCurrentChannelId as jest.Mock).mockReturnValue(of$(currentChannelId));
} as any);
jest.mocked(observeCurrentChannelId).mockReturnValue(of$(currentChannelId));
const result = observeCurrentChannel(database);
@ -1111,10 +1115,10 @@ describe('Channel Functions', () => {
const mockQuery = jest.fn().mockReturnValue({
observe: jest.fn().mockReturnValue(of$([])),
});
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: mockQuery,
});
(observeCurrentChannelId as jest.Mock).mockReturnValue(of$(currentChannelId));
} as any);
jest.mocked(observeCurrentChannelId).mockReturnValue(of$(currentChannelId));
const result = observeCurrentChannel(database);
@ -1128,10 +1132,10 @@ describe('Channel Functions', () => {
describe('getChannelInfo', () => {
it('should return the channel info if found', async () => {
const channelId = 'channel_id';
const mockChannelInfo = {id: channelId} as ChannelInfoModel;
(database.get as jest.Mock).mockReturnValue({
const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: channelId});
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockResolvedValue(mockChannelInfo),
});
} as any);
const result = await getChannelInfo(database, channelId);
@ -1141,9 +1145,9 @@ describe('Channel Functions', () => {
it('should return undefined if the channel info is not found', async () => {
const channelId = 'channel_id';
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
find: jest.fn().mockRejectedValue(new Error('Not found')),
});
} as any);
const result = await getChannelInfo(database, channelId);
@ -1172,14 +1176,12 @@ describe('Channel Membership Functions', () => {
it('should delete channel membership and return models', async () => {
const userId = 'user_id';
const channelId = 'channel_id';
const mockMembership = {
prepareDestroyPermanently: jest.fn().mockReturnValue({}),
} as unknown as ChannelMembershipModel;
(database.get as jest.Mock).mockReturnValue({
const mockMembership = TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})});
jest.mocked(database.get).mockReturnValue({
query: jest.fn().mockReturnValue({
fetch: jest.fn().mockResolvedValue([mockMembership]),
}),
});
} as any);
const result = await deleteChannelMembership(operator, userId, channelId);
@ -1191,14 +1193,12 @@ describe('Channel Membership Functions', () => {
it('should return models without batching if prepareRecordsOnly is true', async () => {
const userId = 'user_id';
const channelId = 'channel_id';
const mockMembership = {
prepareDestroyPermanently: jest.fn().mockReturnValue({}),
} as unknown as ChannelMembershipModel;
(database.get as jest.Mock).mockReturnValue({
const mockMembership = TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})});
jest.mocked(database.get).mockReturnValue({
query: jest.fn().mockReturnValue({
fetch: jest.fn().mockResolvedValue([mockMembership]),
}),
});
} as any);
const result = await deleteChannelMembership(operator, userId, channelId, true);
@ -1211,11 +1211,11 @@ describe('Channel Membership Functions', () => {
const userId = 'user_id';
const channelId = 'channel_id';
const error = new Error('Test error');
(database.get as jest.Mock).mockReturnValue({
jest.mocked(database.get).mockReturnValue({
query: jest.fn().mockReturnValue({
fetch: jest.fn().mockRejectedValue(error),
}),
});
} as any);
const result = await deleteChannelMembership(operator, userId, channelId);
@ -1241,7 +1241,7 @@ describe('Channel Membership Functions', () => {
const userId = 'user_id';
const channelId = 'channel_id';
const error = new Error('Test error');
(operator.handleChannelMembership as jest.Mock).mockRejectedValue(error);
jest.mocked(operator.handleChannelMembership).mockRejectedValue(error);
const result = await addChannelMembership(operator, userId, channelId);
@ -1264,11 +1264,11 @@ describe('Channel Membership Functions', () => {
it('should return the members count by channel IDs', async () => {
const channelsId = ['channel1', 'channel2'];
const mockMemberships = [
{channelId: 'channel1'},
{channelId: 'channel1'},
{channelId: 'channel2'},
] as ChannelMembershipModel[];
(mockQuery as jest.Mock).mockReturnValue({
TestHelper.fakeChannelMembershipModel({channelId: 'channel1'}),
TestHelper.fakeChannelMembershipModel({channelId: 'channel1'}),
TestHelper.fakeChannelMembershipModel({channelId: 'channel2'}),
];
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue(mockMemberships),
});
@ -1281,8 +1281,8 @@ describe('Channel Membership Functions', () => {
it('should return zero counts for channels with no members', async () => {
const channelsId = ['channel1', 'channel2'];
const mockMemberships = [] as ChannelMembershipModel[];
(mockQuery as jest.Mock).mockReturnValue({
const mockMemberships: ChannelMembershipModel[] = [];
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockResolvedValue(mockMemberships),
});
@ -1296,7 +1296,7 @@ describe('Channel Membership Functions', () => {
it('should handle errors gracefully', async () => {
const channelsId = ['channel1', 'channel2'];
const error = new Error('Test error');
(mockQuery as jest.Mock).mockReturnValue({
jest.mocked(mockQuery).mockReturnValue({
fetch: jest.fn().mockRejectedValue(error),
});
@ -1336,9 +1336,9 @@ describe('Channel Observations', () => {
it('should observe all my channel notify props', () => {
const mockSettings = [
{id: 'id1', notifyProps: {mark_unread: 'all'}},
{id: 'id2', notifyProps: {mark_unread: 'mention'}},
] as MyChannelSettingsModel[];
TestHelper.fakeMyChannelSettingsModel({id: 'id1', notifyProps: {mark_unread: 'all'}}),
TestHelper.fakeMyChannelSettingsModel({id: 'id2', notifyProps: {mark_unread: 'mention'}}),
];
mockQuery.mockReturnValue({
observeWithColumns: jest.fn().mockReturnValue(of$(mockSettings)),
});
@ -1356,13 +1356,13 @@ describe('Channel Observations', () => {
it('should observe notify props by channels', () => {
const channels = [
{id: 'channel1'},
{id: 'channel2'},
] as ChannelModel[];
TestHelper.fakeChannelModel({id: 'channel1'}),
TestHelper.fakeChannelModel({id: 'channel2'}),
];
const mockSettings = [
{id: 'channel1', notifyProps: {mark_unread: 'all'}},
{id: 'channel2', notifyProps: {mark_unread: 'mention'}},
] as MyChannelSettingsModel[];
TestHelper.fakeMyChannelSettingsModel({id: 'channel1', notifyProps: {mark_unread: 'all'}}),
TestHelper.fakeMyChannelSettingsModel({id: 'channel2', notifyProps: {mark_unread: 'mention'}}),
];
mockQuery.mockReturnValue({
observeWithColumns: jest.fn().mockReturnValue(of$(mockSettings)),
});
@ -1381,9 +1381,9 @@ describe('Channel Observations', () => {
it('should observe my channel mention count', () => {
const teamId = 'team_id';
const mockChannels = [
{mentionsCount: 2, isUnread: true},
{mentionsCount: 3, isUnread: false},
] as MyChannelModel[];
TestHelper.fakeMyChannelModel({mentionsCount: 2, isUnread: true}),
TestHelper.fakeMyChannelModel({mentionsCount: 3, isUnread: false}),
];
mockQuery.mockReturnValue({
observeWithColumns: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1398,9 +1398,9 @@ describe('Channel Observations', () => {
it('should observe my channel mention count with no team id', () => {
const mockChannels = [
{mentionsCount: 2, isUnread: true},
{mentionsCount: 3, isUnread: false},
] as MyChannelModel[];
TestHelper.fakeMyChannelModel({mentionsCount: 2, isUnread: true}),
TestHelper.fakeMyChannelModel({mentionsCount: 3, isUnread: false}),
];
mockQuery.mockReturnValue({
observeWithColumns: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1416,8 +1416,8 @@ describe('Channel Observations', () => {
it('should observe direct channels by term', () => {
const term = '@test';
const mockCurrentUserId = 'user_id';
const mockChannels = [{id: 'channel1'}] as MyChannelModel[];
(observeCurrentUserId as jest.Mock).mockReturnValue(of$(mockCurrentUserId));
const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})];
jest.mocked(observeCurrentUserId).mockReturnValue(of$(mockCurrentUserId));
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1432,8 +1432,8 @@ describe('Channel Observations', () => {
it('should observe not direct channels by term', () => {
const term = 'test';
const mockTeammateNameSetting = General.TEAMMATE_NAME_DISPLAY.SHOW_USERNAME;
const mockUsers = [{id: 'user1'}] as UserModel[];
(observeTeammateNameDisplay as jest.Mock).mockReturnValue(of$(mockTeammateNameSetting));
const mockUsers = [TestHelper.fakeUserModel({id: 'user1'})];
jest.mocked(observeTeammateNameDisplay).mockReturnValue(of$(mockTeammateNameSetting));
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockUsers)),
});
@ -1447,7 +1447,7 @@ describe('Channel Observations', () => {
it('should observe joined channels by term', () => {
const term = 'test';
const mockChannels = [{id: 'channel1'}] as MyChannelModel[];
const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})];
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1472,7 +1472,7 @@ describe('Channel Observations', () => {
it('should observe archive channels by term', () => {
const term = 'test';
const mockChannels = [{id: 'channel1'}] as MyChannelModel[];
const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})];
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1497,7 +1497,9 @@ describe('Channel Observations', () => {
it('should observe channel settings', () => {
const channelId = 'channel_id';
const mockSettings = {id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))} as unknown as MyChannelSettingsModel;
const mockSettings = TestHelper.fakeMyChannelSettingsModel({id: channelId});
jest.mocked(mockSettings.observe).mockReturnValue(of$(mockSettings));
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$([mockSettings])),
});
@ -1506,13 +1508,14 @@ describe('Channel Observations', () => {
expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS);
result.subscribe((value) => {
expect(value).toEqual({id: channelId});
expect(value).toMatchObject({id: channelId});
});
});
it('should observe is muted setting', () => {
const channelId = 'channel_id';
const mockSettings = {id: channelId, notifyProps: {mark_unread: General.MENTION}, observe: jest.fn().mockReturnValue(of$({id: channelId, notifyProps: {mark_unread: General.MENTION}}))} as unknown as MyChannelSettingsModel;
const mockSettings = TestHelper.fakeMyChannelSettingsModel({id: channelId, notifyProps: {mark_unread: General.MENTION}});
jest.mocked(mockSettings.observe).mockReturnValue(of$(mockSettings));
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$([mockSettings])),
});
@ -1526,8 +1529,8 @@ describe('Channel Observations', () => {
});
it('should observe channels by last post at', () => {
const myChannels = [{id: 'channel1'}] as MyChannelModel[];
const mockChannels = [{id: 'channel1'}] as ChannelModel[];
const myChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})];
const mockChannels = [TestHelper.fakeChannelModel({id: 'channel1'})];
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockChannels)),
});
@ -1542,7 +1545,10 @@ describe('Channel Observations', () => {
it('should observe channel members', () => {
const channelId = 'channel_id';
const mockMembers = [{id: 'member1'}, {id: 'member2'}] as ChannelMembershipModel[];
const mockMembers = [
TestHelper.fakeChannelMembershipModel({id: 'member1'}),
TestHelper.fakeChannelMembershipModel({id: 'member2'}),
];
mockQuery.mockReturnValue({
observe: jest.fn().mockReturnValue(of$(mockMembers)),
});
@ -1580,7 +1586,7 @@ describe('observeMyChannelUnreads', () => {
it('should return true when there are unread channels that are not muted', async () => {
const subscriptionNext = jest.fn();
const notify_props = {mark_unread: 'all' as const};
const notify_props = TestHelper.fakeChannelNotifyProps({mark_unread: 'all'});
const result = observeMyChannelUnreads(database, teamId);
result.subscribe({next: subscriptionNext});
@ -1628,7 +1634,7 @@ describe('observeMyChannelUnreads', () => {
it('should return false when all unread channels are muted', async () => {
const subscriptionNext = jest.fn();
const notify_props = {mark_unread: 'mention' as const};
const notify_props = TestHelper.fakeChannelNotifyProps({mark_unread: General.MENTION});
const result = observeMyChannelUnreads(database, teamId);
result.subscribe({next: subscriptionNext});
@ -1724,7 +1730,7 @@ describe('observeMyChannelUnreads', () => {
it('should not retrigger the subscription when there are changes in other teams', async () => {
const subscriptionNext = jest.fn();
const otherTeamId = 'other_team_id';
const notify_props = {mark_unread: 'all' as const};
const notify_props = TestHelper.fakeChannelNotifyProps({mark_unread: General.MENTION});
const result = observeMyChannelUnreads(database, teamId);
result.subscribe({next: subscriptionNext});

View file

@ -3,6 +3,7 @@
import {General, Permissions} from '@constants';
import DatabaseManager from '@database/manager';
import TestHelper from '@test/test_helper';
import {
queryRoles,
@ -17,10 +18,6 @@ import {
import type ServerDataOperator from '@database/operator/server_data_operator';
import type {Database} from '@nozbe/watermelondb';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type TeamModel from '@typings/database/models/servers/team';
import type UserModel from '@typings/database/models/servers/user';
describe('Role Queries', () => {
const serverUrl = 'baseHandler.test.com';
@ -108,16 +105,16 @@ describe('Role Queries', () => {
describe('observePermissionForChannel', () => {
it('should observe channel permissions', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_user',
} as UserModel;
});
const mockChannel = {
const mockChannel = TestHelper.fakeChannelModel({
id: 'channel1',
type: General.OPEN_CHANNEL,
teamId: 'team1',
} as ChannelModel;
});
operator.handleRole({
roles: [{
@ -146,7 +143,7 @@ describe('Role Queries', () => {
it('should return default value when no user', (done) => {
observePermissionForChannel(
database,
{} as any,
TestHelper.fakeChannelModel(),
undefined,
Permissions.CREATE_POST,
true,
@ -162,14 +159,14 @@ describe('Role Queries', () => {
describe('observePermissionForTeam', () => {
it('should observe team permissions', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_user',
} as UserModel;
});
const mockTeam = {
const mockTeam = TestHelper.fakeTeamModel({
id: 'team1',
} as TeamModel;
});
const myTeams: MyTeam[] = [{
id: mockTeam.id,
@ -214,7 +211,7 @@ describe('Role Queries', () => {
observePermissionForTeam(
database,
undefined,
{} as any,
TestHelper.fakeUserModel(),
Permissions.MANAGE_TEAM,
true,
).subscribe({
@ -229,18 +226,18 @@ describe('Role Queries', () => {
describe('observeCanManageChannelMembers', () => {
it('should observe manage members permission for public channel', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_admin',
} as UserModel;
});
Promise.all([
operator.handleChannel({
channels: [{
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 0,
}] as Channel[],
})],
prepareRecordsOnly: false,
}),
operator.handleRole({
@ -266,18 +263,18 @@ describe('Role Queries', () => {
});
it('should not allow managing default channel members', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_admin',
} as UserModel;
});
operator.handleChannel({
channels: [{
channels: [TestHelper.fakeChannel({
id: 'channel1',
name: General.DEFAULT_CHANNEL,
type: General.OPEN_CHANNEL,
delete_at: 0,
}] as Channel[],
})],
prepareRecordsOnly: false,
}).then(() => {
observeCanManageChannelMembers(
@ -297,23 +294,23 @@ describe('Role Queries', () => {
describe('observePermissionForPost', () => {
it('should observe post permissions', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_user',
} as UserModel;
});
const mockPost = {
const mockPost = TestHelper.fakePostModel({
id: 'post1',
channelId: 'channel1',
} as PostModel;
});
Promise.all([
operator.handleChannel({
channels: [{
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
team_id: 'team1',
}] as Channel[],
})],
prepareRecordsOnly: false,
}),
operator.handleRole({
@ -342,10 +339,10 @@ describe('Role Queries', () => {
});
it('should return default value when no channel exists', (done) => {
const mockPost = {
const mockPost = TestHelper.fakePostModel({
id: 'post1',
channelId: 'nonexistent',
} as PostModel;
});
observePermissionForPost(
database,
@ -365,18 +362,18 @@ describe('Role Queries', () => {
describe('observeCanManageChannelSettings', () => {
it('should observe manage settings permission', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_admin',
} as UserModel;
});
Promise.all([
operator.handleChannel({
channels: [{
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 0,
}] as Channel[],
})],
prepareRecordsOnly: false,
}),
operator.handleRole({
@ -403,17 +400,17 @@ describe('Role Queries', () => {
});
it('should not allow managing deleted channel settings', (done) => {
const mockUser = {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'system_admin',
} as UserModel;
});
operator.handleChannel({
channels: [{
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 123,
}] as Channel[],
})],
prepareRecordsOnly: false,
}).then(() => {
observeCanManageChannelSettings(

View file

@ -2,20 +2,19 @@
// See LICENSE.txt for license information.
import {fireEvent} from '@testing-library/react-native';
import React from 'react';
import React, {type ComponentProps} from 'react';
import {renderWithIntl} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import ProfileForm from './form';
import type {CustomAttributeSet} from '@typings/api/custom_profile_attributes';
import type UserModel from '@typings/database/models/servers/user';
import type {UserInfo} from '@typings/screens/edit_profile';
describe('ProfileForm', () => {
const baseProps = {
const baseProps: ComponentProps<typeof ProfileForm> = {
canSave: false,
currentUser: {
currentUser: TestHelper.fakeUserModel({
id: 'user1',
firstName: 'First',
lastName: 'Last',
@ -24,7 +23,7 @@ describe('ProfileForm', () => {
nickname: 'nick',
position: 'position',
authService: '',
} as UserModel,
}),
isTablet: false,
lockedFirstName: false,
lockedLastName: false,
@ -40,7 +39,7 @@ describe('ProfileForm', () => {
nickname: 'nick',
position: 'position',
customAttributes: {},
} as UserInfo,
},
enableCustomAttributes: false,
};

View file

@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Preferences} from '@constants';
import {BOTTOM_TAB_PROFILE_PHOTO_SIZE, BOTTOM_TAB_STATUS_SIZE} from '@constants/view';
import {renderWithEverything, renderWithIntl} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
@ -9,20 +10,20 @@ import {changeOpacity} from '@utils/theme';
import {Account, default as AccountWithDatabaseObservable} from './account';
import type {UserModel} from '@database/models/server';
import type Database from '@nozbe/watermelondb/Database';
jest.mock('@components/profile_picture', () => 'ProfilePicture');
const currentUser = {
const currentUser = TestHelper.fakeUserModel({
id: 'user1',
username: 'testuser',
} as UserModel;
});
const theme = {
...Preferences.THEMES.denim,
buttonBg: '#000',
centerChannelColor: '#fff',
} as Theme;
};
describe('Account', () => {
const centerChannelColorWithOpacity = changeOpacity(theme.centerChannelColor, 0.48);
@ -64,7 +65,7 @@ describe('Account', () => {
const profilePicture = getByTestId('account-profile-picture');
expect(profilePicture.props.author).toEqual({id: 'user1', username: 'testuser'});
expect(profilePicture.props.author).toEqual(expect.objectContaining({id: 'user1', username: 'testuser'}));
expect(profilePicture.props.showStatus).toBe(true);
expect(profilePicture.props.size).toBe(BOTTOM_TAB_PROFILE_PHOTO_SIZE);
expect(profilePicture.props.statusSize).toBe(BOTTOM_TAB_STATUS_SIZE);

View file

@ -1,34 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getMentionProps, canSaveSettings, getUniqueKeywordsFromInput, type CanSaveSettings} from './mention_settings';
import TestHelper from '@test/test_helper';
import type UserModel from '@typings/database/models/servers/user';
import {getMentionProps, canSaveSettings, getUniqueKeywordsFromInput, type CanSaveSettings} from './mention_settings';
describe('getMentionProps', () => {
test('Should have correct return type when input is empty', () => {
const mentionProps = getMentionProps({notifyProps: {} as UserNotifyProps} as UserModel);
const user = TestHelper.fakeUserModel({notifyProps: null});
const mentionProps = getMentionProps(user);
expect(mentionProps).toEqual({
mentionKeywords: [],
usernameMention: false,
channel: false,
first_name: false,
comments: '',
notifyProps: {},
mentionKeywords: [
`@${user.username}`,
],
usernameMention: true,
channel: true,
first_name: true,
comments: 'any',
notifyProps: expect.any(Object),
});
});
test('Should have correct return type for when channel, first_name, currentUser.username are provided', () => {
const mentionProps = getMentionProps({
const mentionProps = getMentionProps(TestHelper.fakeUserModel({
username: 'testUser',
notifyProps: {
notifyProps: TestHelper.fakeUserNotifyProps({
comments: 'any',
channel: 'true',
first_name: 'true',
mention_keys: 'testUser',
} as UserNotifyProps,
} as UserModel);
}),
}));
expect(mentionProps.mentionKeywords).toEqual([]);
expect(mentionProps.usernameMention).toEqual(true);
@ -37,12 +40,12 @@ describe('getMentionProps', () => {
});
test('Should have correct return type for mention_keys input', () => {
const mentionProps = getMentionProps({
const mentionProps = getMentionProps(TestHelper.fakeUserModel({
username: 'testUser',
notifyProps: {
notifyProps: TestHelper.fakeUserNotifyProps({
mention_keys: 'testUser,testUser2,testKey1,testKey2',
} as UserNotifyProps,
} as UserModel);
}),
}));
expect(mentionProps.mentionKeywords).toHaveLength(3);
expect(mentionProps.mentionKeywords).toEqual(['testUser2', 'testKey1', 'testKey2']);
@ -50,44 +53,51 @@ describe('getMentionProps', () => {
});
describe('canSaveSettings', () => {
const getBaseCanSaveSettingParams = (): CanSaveSettings => ({
mentionKeywords: [],
mentionProps: {
mentionKeywords: [],
usernameMention: true,
channel: true,
first_name: true,
comments: 'any',
notifyProps: TestHelper.fakeUserNotifyProps(),
},
channelMentionOn: true,
replyNotificationType: 'any',
firstNameMentionOn: true,
usernameMentionOn: true,
});
test('Should return true when mentionKeywords have changed', () => {
const canSaveSettingParams = {
mentionKeywords: ['test1', 'test2'],
mentionProps: {
mentionKeywords: ['test1', 'test2', 'test3'],
},
} as CanSaveSettings;
const canSaveSettingParams = getBaseCanSaveSettingParams();
canSaveSettingParams.mentionKeywords = ['test1', 'test2'];
canSaveSettingParams.mentionProps.mentionKeywords = ['test1', 'test2', 'test3'];
expect(canSaveSettings(canSaveSettingParams)).toEqual(true);
});
test('Should return false when mentionKeywords have not changed', () => {
const canSaveSettingParams = {
mentionKeywords: ['test1', 'test2'],
mentionProps: {
mentionKeywords: ['test2', 'test1'],
},
} as CanSaveSettings;
const canSaveSettingParams = getBaseCanSaveSettingParams();
canSaveSettingParams.mentionKeywords = ['test1', 'test2'];
canSaveSettingParams.mentionProps.mentionKeywords = ['test2', 'test1'];
expect(canSaveSettings(canSaveSettingParams)).toEqual(false);
});
test('Should return true when only userName has changed', () => {
const canSaveSettingParams = {
channelMentionOn: true,
replyNotificationType: 'any',
firstNameMentionOn: true,
usernameMentionOn: true,
mentionKeywords: ['test1', 'test2'],
mentionProps: {
channel: true,
comments: 'any' as UserNotifyProps['comments'],
first_name: true,
usernameMention: false,
mentionKeywords: ['test1', 'test2'],
notifyProps: {} as UserNotifyProps,
},
};
const canSaveSettingParams = getBaseCanSaveSettingParams();
canSaveSettingParams.channelMentionOn = true;
canSaveSettingParams.replyNotificationType = 'any';
canSaveSettingParams.firstNameMentionOn = true;
canSaveSettingParams.usernameMentionOn = true;
canSaveSettingParams.mentionKeywords = ['test1', 'test2'];
canSaveSettingParams.mentionProps.channel = true;
canSaveSettingParams.mentionProps.comments = 'any';
canSaveSettingParams.mentionProps.first_name = true;
canSaveSettingParams.mentionProps.usernameMention = false;
canSaveSettingParams.mentionProps.mentionKeywords = ['test1', 'test2'];
canSaveSettingParams.mentionProps.notifyProps = TestHelper.fakeUserNotifyProps();
expect(canSaveSettings(canSaveSettingParams)).toEqual(true);
});

View file

@ -5,11 +5,10 @@ import React from 'react';
import {fetchCustomAttributes} from '@actions/remote/user';
import {renderWithIntlAndTheme, screen, waitFor} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import UserInfo from './user_info';
import type UserModel from '@database/models/server/user';
const localhost = 'http://localhost:8065';
jest.mock('@actions/remote/user', () => ({
@ -23,18 +22,18 @@ jest.mock('@context/server', () => ({
describe('screens/user_profile/UserInfo', () => {
beforeEach(() => {
// Reset mock before each test
(fetchCustomAttributes as jest.Mock).mockReset();
jest.mocked(fetchCustomAttributes).mockReset();
});
const baseProps = {
user: {
user: TestHelper.fakeUserModel({
id: 'user1',
firstName: 'First',
lastName: 'Last',
username: 'username',
nickname: 'nick',
position: 'Developer',
} as UserModel,
}),
isMyUser: false,
showCustomStatus: false,
showLocalTime: true,
@ -65,7 +64,7 @@ describe('screens/user_profile/UserInfo', () => {
};
test('should display custom attributes in sort order', async () => {
(fetchCustomAttributes as jest.Mock).mockResolvedValue({attributes: baseCustomAttributes});
jest.mocked(fetchCustomAttributes).mockResolvedValue({attributes: baseCustomAttributes});
renderWithIntlAndTheme(
<UserInfo {...baseProps}/>,
@ -136,7 +135,7 @@ describe('screens/user_profile/UserInfo', () => {
sort_order: 2,
},
};
(fetchCustomAttributes as jest.Mock).mockResolvedValue({attributes: withEmptyCustomAttributes});
jest.mocked(fetchCustomAttributes).mockResolvedValue({attributes: withEmptyCustomAttributes});
renderWithIntlAndTheme(
<UserInfo {...baseProps}/>,

View file

@ -5,6 +5,7 @@
import {Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import TestHelper from '@test/test_helper';
import {
makeCategoryChannelId,
@ -18,8 +19,6 @@ import {
} from './categories';
import type {ServerDatabase} from '@typings/database/database';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type UserModel from '@typings/database/models/servers/user';
describe('Categories utils', () => {
@ -184,7 +183,7 @@ describe('Categories utils', () => {
const channelModels = await db.operator.handleChannel({channels, prepareRecordsOnly: false});
const myChannelModels = await db.operator.handleMyChannel({channels, myChannels, prepareRecordsOnly: false});
return channelModels.map((channel, index) => {
const myChannel = myChannelModels[index] as MyChannelModel;
const myChannel = myChannelModels[index];
return {
channel,
myChannel,
@ -253,7 +252,7 @@ describe('Categories utils', () => {
const models = await db.operator.handleMyChannel({channels, myChannels, isCRTEnabled: false, prepareRecordsOnly: false});
expect(models).toHaveLength(1);
const myChannel = models[0] as MyChannelModel;
const myChannel = models[0];
let isUnread = isUnreadChannel(myChannel);
expect(isUnread).toBe(true);
await db.database.write(async () => {
@ -355,11 +354,11 @@ describe('Categories utils', () => {
const myChannelModels = await db.operator.handleMyChannel({channels, myChannels, prepareRecordsOnly: false});
const data: ChannelWithMyChannel[] = [{
channel: channelModels[0],
myChannel: myChannelModels[0] as MyChannelModel,
myChannel: myChannelModels[0],
sortOrder: 0,
}, {
channel: channelModels[1],
myChannel: myChannelModels[1] as MyChannelModel,
myChannel: myChannelModels[1],
sortOrder: 1,
}];
let archived = filterArchivedChannels(data, '');
@ -378,19 +377,22 @@ describe('Categories utils', () => {
const now = Date.now();
const dt_chan1 = now - (3 * 24 * 60 * 60 * 1000);
const dt_chan2 = now - (2 * 24 * 60 * 60 * 1000);
const autoclosePrefs = [{
id: 'pref1',
userId: 'me',
category: Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME,
name: 'dm1',
value: `${dt_chan1}`,
}, {
id: 'pref2',
userId: 'me',
category: Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME,
name: 'dm2',
value: `${dt_chan2}`,
}] as PreferenceModel[];
const autoclosePrefs = [
TestHelper.fakePreferenceModel({
id: 'pref1',
userId: 'me',
category: Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME,
name: 'dm1',
value: `${dt_chan1}`,
}),
TestHelper.fakePreferenceModel({
id: 'pref2',
userId: 'me',
category: Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME,
name: 'dm2',
value: `${dt_chan2}`,
}),
];
const channels: Channel[] = [{
id: 'dm1',
@ -472,11 +474,11 @@ describe('Categories utils', () => {
const myChannelModels = await db.operator.handleMyChannel({channels, myChannels, prepareRecordsOnly: false});
const data: ChannelWithMyChannel[] = [{
channel: channelModels[0],
myChannel: myChannelModels[0] as MyChannelModel,
myChannel: myChannelModels[0],
sortOrder: 0,
}, {
channel: channelModels[1],
myChannel: myChannelModels[1] as MyChannelModel,
myChannel: myChannelModels[1],
sortOrder: 0,
}];
@ -491,10 +493,10 @@ describe('Categories utils', () => {
const lastViewedData: ChannelWithMyChannel[] = [{
...data[0],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
...data[0].myChannel,
lastViewedAt: 0,
} as MyChannelModel,
}),
}, data[1]];
const autoclosePrefsNotViewed = [autoclosePrefs[1]];
@ -502,10 +504,10 @@ describe('Categories utils', () => {
expect(result.length).toEqual(1);
const deactivated = new Map<string, UserModel>();
deactivated.set('other1', {
deactivated.set('other1', TestHelper.fakeUserModel({
id: 'other1',
deleteAt: dt_chan1 + 1,
} as UserModel);
}));
result = filterAutoclosedDMs('direct_messages', 3, 'me', '', data, autoclosePrefs, {}, deactivated);
expect(result.length).toEqual(1);
@ -515,29 +517,29 @@ describe('Categories utils', () => {
const mockNotUnread: ChannelWithMyChannel[] = [data[0], {
...data[1],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[1].myChannel.id,
lastViewedAt: 0,
isUnread: false,
} as MyChannelModel,
}),
}];
result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockNotUnread, autoclosePrefs, {}, undefined, 'dm1');
expect(result[0].channel.id).toEqual('dm1');
const mockRecent1: ChannelWithMyChannel[] = [{
...data[0],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[0].myChannel.id,
lastViewedAt: 2,
isUnread: false,
} as MyChannelModel,
}),
}, {
...data[1],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[1].myChannel.id,
lastViewedAt: 1,
isUnread: false,
} as MyChannelModel,
}),
}];
result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockRecent1, [], {});
@ -545,18 +547,18 @@ describe('Categories utils', () => {
const mockRecent2: ChannelWithMyChannel[] = [{
...data[0],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[0].myChannel.id,
lastViewedAt: 1,
isUnread: false,
} as MyChannelModel,
}),
}, {
...data[1],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[1].myChannel.id,
lastViewedAt: 2,
isUnread: false,
} as MyChannelModel,
}),
}];
result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockRecent2, [], {});
@ -564,18 +566,18 @@ describe('Categories utils', () => {
const mockSameLastViewed: ChannelWithMyChannel[] = [{
...data[0],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[0].myChannel.id,
lastViewedAt: 1,
isUnread: false,
} as MyChannelModel,
}),
}, {
...data[1],
myChannel: {
myChannel: TestHelper.fakeMyChannelModel({
id: data[1].myChannel.id,
lastViewedAt: 1,
isUnread: false,
} as MyChannelModel,
}),
}];
result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockSameLastViewed, [], {});
@ -585,25 +587,29 @@ describe('Categories utils', () => {
test('filterManuallyClosedDms', async () => {
const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const closedDMsPrefs = [{
id: 'pref1',
userId: 'me',
category: Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW,
name: 'dm1',
value: 'true',
}, {
id: 'pref2',
userId: 'me',
category: Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW,
name: 'dm2',
value: 'true',
}, {
id: 'pref3',
userId: 'me',
category: Preferences.CATEGORIES.GROUP_CHANNEL_SHOW,
name: 'gm1',
value: 'false',
}] as PreferenceModel[];
const closedDMsPrefs = [
TestHelper.fakePreferenceModel({
id: 'pref1',
userId: 'me',
category: Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW,
name: 'dm1',
value: 'true',
}),
TestHelper.fakePreferenceModel({
id: 'pref2',
userId: 'me',
category: Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW,
name: 'dm2',
value: 'true',
}),
TestHelper.fakePreferenceModel({
id: 'pref3',
userId: 'me',
category: Preferences.CATEGORIES.GROUP_CHANNEL_SHOW,
name: 'gm1',
value: 'false',
}),
];
const {channels, myChannels} = buildChannelsData();
const data = await createChannelsDbRecords(db, channels, myChannels);

View file

@ -8,6 +8,7 @@ import {getUsersCountFromMentions} from '@actions/local/post';
import {General, Post} from '@constants';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import {getUserById} from '@queries/servers/user';
import TestHelper from '@test/test_helper';
import {toMilliseconds} from '@utils/datetime';
import {
@ -28,9 +29,6 @@ import {
persistentNotificationsConfirmation,
} from '.';
import type PostModel from '@typings/database/models/servers/post';
import type UserModel from '@typings/database/models/servers/user';
jest.mock('@actions/local/post', () => ({
getUsersCountFromMentions: jest.fn(),
}));
@ -48,32 +46,32 @@ jest.mock('@database/manager', () => ({
describe('post utils', () => {
describe('areConsecutivePosts', () => {
it('should return true for consecutive posts from the same user within the collapse timeout', () => {
const post = {
const post = TestHelper.fakePostModel({
userId: 'user1',
createAt: 1000,
props: {},
} as PostModel;
const previousPost = {
});
const previousPost = TestHelper.fakePostModel({
userId: 'user1',
createAt: 500,
props: {},
} as PostModel;
});
const result = areConsecutivePosts(post, previousPost);
expect(result).toBe(true);
});
it('should return false for posts from different users', () => {
const post = {
const post = TestHelper.fakePostModel({
userId: 'user1',
createAt: 1000,
props: {},
} as PostModel;
const previousPost = {
});
const previousPost = TestHelper.fakePostModel({
userId: 'user2',
createAt: 500,
props: {},
} as PostModel;
});
const result = areConsecutivePosts(post, previousPost);
expect(result).toBe(false);
@ -82,22 +80,22 @@ describe('post utils', () => {
describe('isFromWebhook', () => {
it('should return true for posts from a webhook', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'true',
} as Record<string, unknown>,
} as PostModel;
},
});
const result = isFromWebhook(post);
expect(result).toBe(true);
});
it('should return false for posts not from a webhook', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
} as Record<string, unknown>,
} as PostModel;
},
});
const result = isFromWebhook(post);
expect(result).toBe(false);
@ -106,18 +104,18 @@ describe('post utils', () => {
describe('isEdited', () => {
it('should return true if the post is edited', () => {
const post = {
const post = TestHelper.fakePostModel({
editAt: 1000,
} as PostModel;
});
const result = isEdited(post);
expect(result).toBe(true);
});
it('should return false if the post is not edited', () => {
const post = {
const post = TestHelper.fakePostModel({
editAt: 0,
} as PostModel;
});
const result = isEdited(post);
expect(result).toBe(false);
@ -126,18 +124,18 @@ describe('post utils', () => {
describe('isPostEphemeral', () => {
it('should return true for an ephemeral post', () => {
const post = {
const post = TestHelper.fakePostModel({
type: Post.POST_TYPES.EPHEMERAL,
} as PostModel;
});
const result = isPostEphemeral(post);
expect(result).toBe(true);
});
it('should return false for a non-ephemeral post', () => {
const post = {
type: 'normal',
} as PostModel;
const post = TestHelper.fakePostModel({
type: '',
});
const result = isPostEphemeral(post);
expect(result).toBe(false);
@ -146,38 +144,38 @@ describe('post utils', () => {
describe('isPostFailed', () => {
it('should return true if the post has failed prop', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
failed: true,
} as Record<string, unknown>,
},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
} as PostModel;
});
const result = isPostFailed(post);
expect(result).toBe(true);
});
it('should return true if the post is pending and the update time has exceeded the failure time', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
} as PostModel;
});
const result = isPostFailed(post);
expect(result).toBe(true);
});
it('should return false if the post is not failed', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {},
pendingPostId: 'id',
id: 'id',
updateAt: Date.now(),
} as PostModel;
});
const result = isPostFailed(post);
expect(result).toBe(false);
@ -186,34 +184,34 @@ describe('post utils', () => {
describe('isPostPendingOrFailed', () => {
it('should return true if the post is pending', () => {
const post = {
const post = TestHelper.fakePostModel({
pendingPostId: 'id',
id: 'id',
props: {},
} as PostModel;
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(true);
});
it('should return true if the post has failed', () => {
const post = {
const post = TestHelper.fakePostModel({
pendingPostId: 'id',
id: 'id',
updateAt: Date.now() - Post.POST_TIME_TO_FAIL - 1000,
props: {},
} as PostModel;
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(true);
});
it('should return false if the post is neither pending nor failed', () => {
const post = {
const post = TestHelper.fakePostModel({
pendingPostId: 'differentId',
id: 'id',
props: {},
} as PostModel;
});
const result = isPostPendingOrFailed(post);
expect(result).toBe(false);
@ -222,18 +220,18 @@ describe('post utils', () => {
describe('isSystemMessage', () => {
it('should return true if the post is a system message', () => {
const post = {
type: `${Post.POST_TYPES.SYSTEM_MESSAGE_PREFIX}any_type`,
} as PostModel;
const post = TestHelper.fakePostModel({
type: `${Post.POST_TYPES.SYSTEM_MESSAGE_PREFIX}any_type` as PostType,
});
const result = isSystemMessage(post);
expect(result).toBe(true);
});
it('should return false if the post is not a system message', () => {
const post = {
type: 'normal_type',
} as PostModel;
const post = TestHelper.fakePostModel({
type: 'add_bot_teams_channels',
});
const result = isSystemMessage(post);
expect(result).toBe(false);
@ -290,18 +288,18 @@ describe('post utils', () => {
describe('fromAutoResponder', () => {
it('should return true if the post is from an auto responder', () => {
const post = {
const post = TestHelper.fakePostModel({
type: Post.POST_TYPES.SYSTEM_AUTO_RESPONDER,
} as PostModel;
});
const result = fromAutoResponder(post);
expect(result).toBe(true);
});
it('should return false if the post is not from an auto responder', () => {
const post = {
type: 'normal_type',
} as PostModel;
const post = TestHelper.fakePostModel({
type: 'add_bot_teams_channels',
});
const result = fromAutoResponder(post);
expect(result).toBe(false);
@ -320,8 +318,8 @@ describe('post utils', () => {
const intl = createIntl({locale: DEFAULT_LOCALE, messages: getTranslations(DEFAULT_LOCALE)});
it('should show alert with DM channel description when channelType is DM_CHANNEL', async () => {
const mockUser = {username: 'teammate'};
(getUserById as jest.Mock).mockResolvedValue(mockUser);
const mockUser = TestHelper.fakeUserModel({username: 'teammate'});
jest.mocked(getUserById).mockResolvedValue(mockUser);
await persistentNotificationsConfirmation(
serverUrl,
@ -376,7 +374,7 @@ describe('post utils', () => {
});
it('should show alert when no mentions found', async () => {
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(0);
jest.mocked(getUsersCountFromMentions).mockResolvedValue(0);
await persistentNotificationsConfirmation(
serverUrl,
@ -404,7 +402,7 @@ describe('post utils', () => {
});
it('should show alert when mentions exceed max recipients', async () => {
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(15);
jest.mocked(getUsersCountFromMentions).mockResolvedValue(15);
await persistentNotificationsConfirmation(
serverUrl,
@ -435,7 +433,7 @@ describe('post utils', () => {
});
it('should show confirmation alert for valid mentions within limit', async () => {
(getUsersCountFromMentions as jest.Mock).mockResolvedValue(5);
jest.mocked(getUsersCountFromMentions).mockResolvedValue(5);
await persistentNotificationsConfirmation(
serverUrl,
@ -467,42 +465,42 @@ describe('post utils', () => {
describe('postUserDisplayName', () => {
it('should return the override username if from webhook and override is enabled', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'true',
override_username: 'webhook_user',
} as Record<string, unknown>,
} as PostModel;
},
});
const result = postUserDisplayName(post, undefined, undefined, true);
expect(result).toBe('webhook_user');
});
it('should return the authors display name if not from webhook or override is disabled', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
} as Record<string, unknown>,
} as PostModel;
const author = {
},
});
const author = TestHelper.fakeUserModel({
username: 'user1',
locale: 'en',
} as UserModel;
});
const result = postUserDisplayName(post, author, undefined, false);
expect(result).toBe('user1');
});
it('should return the authors display name using the teammate name display', () => {
const post = {
const post = TestHelper.fakePostModel({
props: {
from_webhook: 'false',
} as Record<string, unknown>,
} as PostModel;
const author = {
},
});
const author = TestHelper.fakeUserModel({
username: 'user1',
locale: 'en',
} as UserModel;
});
const result = postUserDisplayName(post, author, 'nickname', false);
expect(result).toBe('user1');
@ -511,18 +509,18 @@ describe('post utils', () => {
describe('shouldIgnorePost', () => {
it('should return true if the post type is in the ignore list', () => {
const post = {
const post = TestHelper.fakePost({
type: Post.POST_TYPES.CHANNEL_DELETED,
} as Post;
});
const result = shouldIgnorePost(post);
expect(result).toBe(true);
});
it('should return false if the post type is not in the ignore list', () => {
const post = {
const post = TestHelper.fakePost({
type: Post.POST_TYPES.EPHEMERAL,
} as Post;
});
const result = shouldIgnorePost(post);
expect(result).toBe(false);
@ -534,17 +532,17 @@ describe('post utils', () => {
const data = {
order: ['post1', 'post2'],
posts: {
post1: {id: 'post1', message: 'First post'},
post2: {id: 'post2', message: 'Second post'},
post1: TestHelper.fakePost({id: 'post1', message: 'First post'}),
post2: TestHelper.fakePost({id: 'post2', message: 'Second post'}),
},
prev_post_id: 'post0',
} as unknown as PostResponse;
};
const result = processPostsFetched(data);
expect(result).toEqual({
posts: [
{id: 'post1', message: 'First post'},
{id: 'post2', message: 'Second post'},
expect.objectContaining({id: 'post1', message: 'First post'}),
expect.objectContaining({id: 'post2', message: 'Second post'}),
],
order: ['post1', 'post2'],
previousPostId: 'post0',
@ -555,9 +553,9 @@ describe('post utils', () => {
describe('getLastFetchedAtFromPosts', () => {
it('should return the maximum timestamp from the posts', () => {
const posts = [
{create_at: 1000, update_at: 2000, delete_at: 0},
{create_at: 1500, update_at: 2500, delete_at: 3000},
] as Post[];
TestHelper.fakePost({create_at: 1000, update_at: 2000, delete_at: 0}),
TestHelper.fakePost({create_at: 1500, update_at: 2500, delete_at: 3000}),
];
const result = getLastFetchedAtFromPosts(posts);
expect(result).toBe(3000);

View file

@ -11,7 +11,7 @@ import DatabaseManager from '@database/manager';
import {DEFAULT_LOCALE} from '@i18n';
import {getUserById} from '@queries/servers/user';
import {toMilliseconds} from '@utils/datetime';
import {ensureString} from '@utils/types';
import {ensureString, includes} from '@utils/types';
import {displayUsername, getUserIdFromChannelName} from '@utils/user';
import type PostModel from '@typings/database/models/servers/post';
@ -78,7 +78,7 @@ export function postUserDisplayName(post: PostModel, author?: UserModel, teammat
}
export function shouldIgnorePost(post: Post): boolean {
return Post.IGNORE_POST_TYPES.includes(post.type);
return includes(Post.IGNORE_POST_TYPES, post.type);
}
export const processPostsFetched = (data: PostResponse) => {

View file

@ -593,8 +593,8 @@ describe('preparePostList', () => {
it('should combine user activity posts correctly', () => {
const posts = [
mockPostModel({id: 'post1', type: 'user-activity', props: {user_activity_posts: []}}),
mockPostModel({id: 'post2', type: 'user-activity', props: {user_activity_posts: []}}),
mockPostModel({id: 'post1', type: 'system_combined_user_activity', props: {user_activity_posts: []}}),
mockPostModel({id: 'post2', type: 'system_combined_user_activity', props: {user_activity_posts: []}}),
];
const result = preparePostList(posts, lastViewedAt, true, currentUserId, currentUsername, showJoinLeave, currentTimezone, isThreadScreen, savedPostIds);
@ -613,7 +613,7 @@ describe('preparePostList', () => {
id: 'deleted-post',
deleteAt: Date.now(), // Set deleteAt to a non-zero value
createAt: Date.now(),
type: 'post', // Ensure it's a 'post' type
type: '',
userId: 'user-id',
message: 'This is a deleted post',
props: {},
@ -732,7 +732,7 @@ describe('shouldFilterJoinLeavePost', () => {
const currentUsername = 'current-username';
it('should not filter non-join/leave posts', () => {
const post = mockPostModel({type: 'custom_post_type'});
const post = mockPostModel({type: 'custom_post_type' as PostType});
const result = shouldFilterJoinLeavePost(post, false, currentUsername);
// Non-join/leave posts should not be filtered regardless of showJoinLeave value

View file

@ -6,7 +6,7 @@ import moment from 'moment-timezone';
import {Post} from '@constants';
import {toMilliseconds} from '@utils/datetime';
import {isFromWebhook} from '@utils/post';
import {ensureString, isArrayOf, isStringArray} from '@utils/types';
import {ensureString, includes, isArrayOf, isStringArray, secureGetFromRecord} from '@utils/types';
import type {PostList, PostWithPrevAndNext} from '@typings/components/post_list';
import type PostModel from '@typings/database/models/servers/post';
@ -73,7 +73,7 @@ function combineUserActivityPosts(orderedPosts: PostList) {
lastPostIsUserActivity = false;
combinedCount = 0;
} else {
const postIsUserActivity = item.type === 'post' && Post.USER_ACTIVITY_POST_TYPES.includes(item.value.currentPost.type);
const postIsUserActivity = item.type === 'post' && includes(Post.USER_ACTIVITY_POST_TYPES, item.value.currentPost.type);
if (postIsUserActivity && lastPostIsUserActivity && combinedCount < MAX_COMBINED_SYSTEM_POSTS) {
out[out.length - 1].value += '_' + item.value.currentPost.id;
} else if (postIsUserActivity) {
@ -96,21 +96,26 @@ function combineUserActivityPosts(orderedPosts: PostList) {
return out;
}
function comparePostTypes(a: typeof postTypePriority, b: typeof postTypePriority) {
return postTypePriority[a.postType] - postTypePriority[b.postType];
function comparePostTypes(a: MessageData, b: MessageData) {
const aPriority = secureGetFromRecord(postTypePriority, a.postType) ?? 99;
const bPriority = secureGetFromRecord(postTypePriority, b.postType) ?? 99;
return aPriority - bPriority;
}
function extractUserActivityData(userActivities: any) {
const messageData: any[] = [];
function extractUserActivityData(userActivities: Record<PostType, UserActivityValue>): UserActivityProp {
const messageData: MessageData[] = [];
const allUserIds: string[] = [];
const allUsernames: string[] = [];
Object.entries(userActivities).forEach(([postType, values]: [string, any]) => {
Object.entries(userActivities).forEach(([postType, values]: [PostType, UserActivityValue]) => {
if (
postType === Post.POST_TYPES.ADD_TO_TEAM ||
postType === Post.POST_TYPES.ADD_TO_CHANNEL ||
postType === Post.POST_TYPES.REMOVE_FROM_CHANNEL
) {
Object.keys(values).map((key) => [key, values[key]]).forEach(([actorId, users]) => {
if (Array.isArray(values)) {
throw new Error('Invalid Post activity data');
}
Object.entries(values).forEach(([actorId, users]) => {
if (Array.isArray(users)) {
throw new Error('Invalid Post activity data');
}
@ -256,10 +261,15 @@ export function selectOrderedPosts(
return out.reverse();
}
type ActivityData = {
ids: string[];
usernames: string[];
}
type UserActivityValue = string[] | Record<string, ActivityData>
function combineUserActivitySystemPost(systemPosts: PostModel[]) {
const userActivities = systemPosts.reduce((acc: any, post: PostModel) => {
const userActivities = systemPosts.reduce((acc: Record<string, UserActivityValue>, post: PostModel) => {
const postType = post.type;
let userActivityProps = acc;
const userActivityProps = acc;
const combinedPostType = userActivityProps[postType];
if (
@ -270,10 +280,12 @@ function combineUserActivitySystemPost(systemPosts: PostModel[]) {
const userId = ensureString(post.props?.addedUserId) || ensureString(post.props?.removedUserId);
const username = ensureString(post.props?.addedUsername) || ensureString(post.props?.removedUsername);
if (combinedPostType) {
if (Array.isArray(combinedPostType[post.userId])) {
if (Array.isArray(combinedPostType)) {
throw new Error('Invalid Post activity data');
}
const users = combinedPostType[post.userId] || {ids: [], usernames: []};
const userCombinedPostType = combinedPostType[post.userId];
const users = userCombinedPostType || {ids: [], usernames: []};
if (userId) {
if (!users.ids.includes(userId)) {
users.ids.push(userId);
@ -308,7 +320,7 @@ function combineUserActivitySystemPost(systemPosts: PostModel[]) {
userActivityProps[postType] = [...combinedPostType, propsUserId];
}
} else {
userActivityProps = {...userActivityProps, [postType]: [propsUserId]};
userActivityProps[postType] = [propsUserId];
}
}
@ -348,7 +360,7 @@ export function generateCombinedPost(combinedId: string, systemPosts: PostModel[
user_activity_posts: systemPosts,
system_post_ids: systemPosts.map((post) => post.id),
},
type: Post.POST_TYPES.COMBINED_USER_ACTIVITY as PostType,
type: Post.POST_TYPES.COMBINED_USER_ACTIVITY,
user_id: '',
metadata: {},
};
@ -380,7 +392,7 @@ export function shouldFilterJoinLeavePost(post: PostModel, showJoinLeave: boolea
}
// Don't filter out non-join/leave messages
if (joinLeavePostTypes.indexOf(post.type) === -1) {
if (!includes(joinLeavePostTypes, post.type)) {
return false;
}
@ -390,7 +402,7 @@ export function shouldFilterJoinLeavePost(post: PostModel, showJoinLeave: boolea
export type MessageData = {
actorId?: string;
postType: string;
postType: PostType;
userIds: string[];
}

View file

@ -40,3 +40,7 @@ export function ensureNumber(v: unknown): number {
export function secureGetFromRecord<T>(v: Record<string, T> | undefined, key: string) {
return typeof v === 'object' && v && Object.prototype.hasOwnProperty.call(v, key) ? v[key] : undefined;
}
export function includes<T extends U, U>(array: T[], value: U) {
return array.includes(value as T);
}

View file

@ -135,7 +135,7 @@ describe('UrlUtils', () => {
const SERVER_WITH_SUBPATH = `http://${URL_PATH_NO_PROTOCOL}`;
const DEEPLINK_URL_ROOT = `mattermost://${URL_NO_PROTOCOL}`;
const DM_USER = TestHelper.fakeUserWithId();
const DM_USER = TestHelper.fakeUser();
const GM_CHANNEL_NAME = '4862db64e76a321d167fe6677f16e96e9275dabe';
const tests = [

View file

@ -7,6 +7,7 @@ import {Alert} from 'react-native';
import {Preferences} from '@constants';
import {Ringtone} from '@constants/calls';
import TestHelper from '@test/test_helper';
import {
confirmOutOfOfficeDisabled,
@ -38,16 +39,15 @@ import {
removeUserFromList,
} from './index';
import type {UserModel} from '@database/models/server';
import type {IntlShape} from 'react-intl';
describe('displayUsername', () => {
const user = {
const user = TestHelper.fakeUser({
username: 'johndoe',
first_name: 'John',
last_name: 'Doe',
nickname: 'Johnny',
} as UserProfile;
});
it('should return the nickname if teammateDisplayNameSetting is DISPLAY_PREFER_NICKNAME', () => {
const result = displayUsername(user, 'en', Preferences.DISPLAY_PREFER_NICKNAME);
@ -80,32 +80,32 @@ describe('displayUsername', () => {
});
it('should return the username if nickname and full name are not available', () => {
const userWithoutNicknameAndFullName = {
const userWithoutNicknameAndFullName = TestHelper.fakeUser({
username: 'johndoe',
first_name: '',
last_name: '',
nickname: '',
} as UserProfile;
});
const result = displayUsername(userWithoutNicknameAndFullName, 'en', Preferences.DISPLAY_PREFER_NICKNAME);
expect(result).toBe('johndoe');
});
it('should return the username if name is empty or whitespace', () => {
const userWithEmptyName = {
const userWithEmptyName = TestHelper.fakeUser({
username: 'johndoe',
first_name: '',
last_name: '',
nickname: '',
} as UserProfile;
});
const result = displayUsername(userWithEmptyName, 'en', Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('johndoe');
});
});
describe('displayGroupMessageName', () => {
const user1 = {id: 'user1', username: 'john_doe', first_name: 'John', last_name: 'Doe'} as UserProfile;
const user2 = {id: 'user2', username: 'jane_doe', first_name: 'Jane', last_name: 'Doe'} as UserProfile;
const user3 = {id: 'user3', username: 'alice_smith', first_name: 'Alice', last_name: 'Smith'} as UserProfile;
const user1 = TestHelper.fakeUser({id: 'user1', username: 'john_doe', first_name: 'John', last_name: 'Doe'});
const user2 = TestHelper.fakeUser({id: 'user2', username: 'jane_doe', first_name: 'Jane', last_name: 'Doe'});
const user3 = TestHelper.fakeUser({id: 'user3', username: 'alice_smith', first_name: 'Alice', last_name: 'Smith'});
it('should return a comma-separated list of usernames', () => {
const users = [user1, user2, user3];
@ -133,7 +133,7 @@ describe('displayGroupMessageName', () => {
});
it('should fallback to username if full name or nickname is not available', () => {
const userWithEmptyName = {id: 'user4', username: 'empty_name', first_name: '', last_name: ''} as UserProfile;
const userWithEmptyName = TestHelper.fakeUser({id: 'user4', username: 'empty_name', first_name: '', last_name: ''});
const users = [userWithEmptyName, user2, user3];
const result = displayGroupMessageName(users, undefined, Preferences.DISPLAY_PREFER_FULL_NAME);
expect(result).toBe('Alice Smith, empty_name, Jane Doe');
@ -148,31 +148,31 @@ describe('displayGroupMessageName', () => {
describe('getFullName', () => {
it('should return the full name if both first and last names are provided - UserProfile', () => {
const user = {first_name: 'John', last_name: 'Doe'} as UserProfile;
const user = TestHelper.fakeUser({first_name: 'John', last_name: 'Doe'});
const result = getFullName(user);
expect(result).toBe('John Doe');
});
it('should return the full name if both first and last names are provided - UserModel', () => {
const user = {firstName: 'John', lastName: 'Doe'} as UserModel;
const user = TestHelper.fakeUserModel({firstName: 'John', lastName: 'Doe'});
const result = getFullName(user);
expect(result).toBe('John Doe');
});
it('should return the first name if only the first name is provided', () => {
const user = {first_name: 'John', last_name: ''} as UserProfile;
const user = TestHelper.fakeUser({first_name: 'John', last_name: ''});
const result = getFullName(user);
expect(result).toBe('John');
});
it('should return the last name if only the last name is provided', () => {
const user = {first_name: '', last_name: 'Doe'} as UserProfile;
const user = TestHelper.fakeUser({first_name: '', last_name: 'Doe'});
const result = getFullName(user);
expect(result).toBe('Doe');
});
it('should return an empty string if neither first nor last name is provided', () => {
const user = {first_name: '', last_name: ''} as UserProfile;
const user = TestHelper.fakeUser({first_name: '', last_name: ''});
const result = getFullName(user);
expect(result).toBe('');
});
@ -255,8 +255,8 @@ describe('isChannelAdmin', () => {
describe('getUsersByUsername', () => {
it('should return a dictionary of users by username', () => {
const users = [
{username: 'john_doe'} as UserModel,
{username: 'jane_doe'} as UserModel,
TestHelper.fakeUserModel({username: 'john_doe'}),
TestHelper.fakeUserModel({username: 'jane_doe'}),
];
const result = getUsersByUsername(users);
expect(result).toEqual({
@ -268,7 +268,7 @@ describe('getUsersByUsername', () => {
describe('getUserTimezoneProps', () => {
it('should return the user timezone props if they exist', () => {
const user = {timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}} as UserModel;
const user = TestHelper.fakeUserModel({timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}});
const result = getUserTimezoneProps(user);
expect(result).toEqual({
useAutomaticTimezone: true,
@ -278,7 +278,7 @@ describe('getUserTimezoneProps', () => {
});
it('should return default timezone props if they do not exist', () => {
const user = {} as UserModel;
const user = TestHelper.fakeUserModel({timezone: null});
const result = getUserTimezoneProps(user);
expect(result).toEqual({
useAutomaticTimezone: true,
@ -290,13 +290,13 @@ describe('getUserTimezoneProps', () => {
describe('getUserTimezone', () => {
it('should return the user timezone', () => {
const user = {timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}} as UserModel;
const user = TestHelper.fakeUserModel({timezone: {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'}});
const result = getUserTimezone(user);
expect(result).toBe('America/New_York');
});
it('should return an empty string if the user timezone does not exist', () => {
const user = {} as UserModel;
const user = TestHelper.fakeUserModel({timezone: null});
const result = getUserTimezone(user);
expect(result).toBe('');
});
@ -304,13 +304,13 @@ describe('getUserTimezone', () => {
describe('getTimezone', () => {
it('should return the automatic timezone if useAutomaticTimezone is true', () => {
const timezone = {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'} as UserTimezone;
const timezone: UserTimezone = {useAutomaticTimezone: 'true', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'};
const result = getTimezone(timezone);
expect(result).toBe('America/New_York');
});
it('should return the manual timezone if useAutomaticTimezone is false', () => {
const timezone = {useAutomaticTimezone: 'false', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'} as UserTimezone;
const timezone: UserTimezone = {useAutomaticTimezone: 'false', automaticTimezone: 'America/New_York', manualTimezone: 'America/Los_Angeles'};
const result = getTimezone(timezone);
expect(result).toBe('America/Los_Angeles');
});
@ -337,10 +337,10 @@ describe('getTimezoneRegion', () => {
describe('getUserCustomStatus', () => {
it('should return the custom status if it exists', () => {
const user = {
const user = TestHelper.fakeUser({
username: 'johndoe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2023-12-31T23:59:59Z"}'} as UserProps,
} as UserProfile;
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2023-12-31T23:59:59Z"}'},
});
const result = getUserCustomStatus(user);
expect(result).toEqual({
emoji: 'smile',
@ -351,7 +351,7 @@ describe('getUserCustomStatus', () => {
});
it('should return undefined if the custom status does not exist', () => {
const user = {} as UserModel;
const user = TestHelper.fakeUserModel();
const result = getUserCustomStatus(user);
expect(result).toBeUndefined();
});
@ -359,23 +359,25 @@ describe('getUserCustomStatus', () => {
describe('isCustomStatusExpired', () => {
it('should return true if the custom status is expired', () => {
const user = {username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2020-12-31T23:59:59Z"}'} as UserProps,
} as UserProfile;
const user = TestHelper.fakeUser({
username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2020-12-31T23:59:59Z"}'},
});
const result = isCustomStatusExpired(user);
expect(result).toBe(true);
});
it('should return false if the custom status is not expired', () => {
const user = {username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2099-12-31T23:59:59Z"}'} as UserProps,
} as UserProfile;
const user = TestHelper.fakeUser({
username: 'john_doe',
props: {customStatus: '{"emoji": "smile", "text": "Happy", "duration": "today", "expires_at": "2099-12-31T23:59:59Z"}'},
});
const result = isCustomStatusExpired(user);
expect(result).toBe(false);
});
it('should return true if the custom status does not exist', () => {
const user = {} as UserModel;
const user = TestHelper.fakeUserModel();
const result = isCustomStatusExpired(user);
expect(result).toBe(true);
});
@ -383,13 +385,13 @@ describe('isCustomStatusExpired', () => {
describe('isBot', () => {
it('should return true if the user is a bot', () => {
const user = {isBot: true} as UserModel;
const user = TestHelper.fakeUserModel({isBot: true});
const result = isBot(user);
expect(result).toBe(true);
});
it('should return false if the user is not a bot', () => {
const user = {isBot: false} as UserModel;
const user = TestHelper.fakeUserModel({isBot: false});
const result = isBot(user);
expect(result).toBe(false);
});
@ -397,13 +399,13 @@ describe('isBot', () => {
describe('isShared', () => {
it('should return true if the user is shared', () => {
const user = {remoteId: 'remote_id'} as UserModel;
const user = TestHelper.fakeUserModel({remoteId: 'remote_id'});
const result = isShared(user);
expect(result).toBe(true);
});
it('should return false if the user is not shared', () => {
const user = {remoteId: ''} as UserModel;
const user = TestHelper.fakeUserModel({remoteId: ''});
const result = isShared(user);
expect(result).toBe(false);
});
@ -437,8 +439,8 @@ describe('getSuggestionsSplitByMultiple', () => {
describe('filterProfilesMatchingTerm', () => {
const users = [
{username: 'john_doe', first_name: 'John', last_name: 'Doe', nickname: 'Johnny', email: 'john@example.com'} as UserProfile,
{username: 'jane_doe', first_name: 'Jane', last_name: 'Doe', nickname: 'Janey', email: 'jane@example.com'} as UserProfile,
TestHelper.fakeUser({username: 'john_doe', first_name: 'John', last_name: 'Doe', nickname: 'Johnny', email: 'john@example.com'}),
TestHelper.fakeUser({username: 'jane_doe', first_name: 'Jane', last_name: 'Doe', nickname: 'Janey', email: 'jane@example.com'}),
];
it('should filter users matching the term', () => {
@ -468,13 +470,13 @@ describe('filterProfilesMatchingTerm', () => {
describe('getNotificationProps', () => {
it('should return the user notification props if they exist', () => {
const user = {notifyProps: {channel: 'false', comments: 'never'}} as UserModel;
const user = TestHelper.fakeUserModel({notifyProps: TestHelper.fakeUserNotifyProps({channel: 'false', comments: 'never'})});
const result = getNotificationProps(user);
expect(result).toEqual(user.notifyProps);
});
it('should return default notification props if they do not exist', () => {
const user = {} as UserModel;
const user = TestHelper.fakeUserModel({firstName: '', notifyProps: null});
const result = getNotificationProps(user);
expect(result).toEqual({
channel: 'true',
@ -484,7 +486,7 @@ describe('getNotificationProps', () => {
email: 'true',
first_name: 'false',
mark_unread: 'all',
mention_keys: '',
mention_keys: `${user.username},@${user.username}`,
highlight_keys: '',
push: 'mention',
push_status: 'online',
@ -531,19 +533,19 @@ describe('getEmailIntervalTexts', () => {
describe('getLastPictureUpdate', () => {
it('should return bot_last_icon_update if the user is a bot', () => {
const user = {isBot: true, props: {bot_last_icon_update: 12345} as UserProps} as UserModel;
const user = TestHelper.fakeUserModel({isBot: true, props: {bot_last_icon_update: 12345}});
const result = getLastPictureUpdate(user);
expect(result).toBe(12345);
});
it('should return lastPictureUpdate if the user is not a bot', () => {
const user = {isBot: false, lastPictureUpdate: 67890} as UserModel;
const user = TestHelper.fakeUserModel({isBot: false, lastPictureUpdate: 67890});
const result = getLastPictureUpdate(user);
expect(result).toBe(67890);
});
it('should return 0 if lastPictureUpdate is not available', () => {
const user = {isBot: false} as UserModel;
const user = TestHelper.fakeUserModel({isBot: false});
const result = getLastPictureUpdate(user);
expect(result).toBe(0);
});
@ -551,34 +553,34 @@ describe('getLastPictureUpdate', () => {
describe('isDeactivated', () => {
it('should return true if the user is deactivated', () => {
const user = {delete_at: 12345} as UserProfile;
const user = TestHelper.fakeUser({delete_at: 12345});
const result = isDeactivated(user);
expect(result).toBe(true);
});
it('should return false if the user is not deactivated', () => {
const user = {delete_at: 0} as UserProfile;
const user = TestHelper.fakeUser({delete_at: 0});
const result = isDeactivated(user);
expect(result).toBe(false);
});
it('should return true if the user is deactivated using deleteAt', () => {
const user = {deleteAt: 12345} as UserModel;
const user = TestHelper.fakeUserModel({deleteAt: 12345});
const result = isDeactivated(user);
expect(result).toBe(true);
});
it('should return false if the user is not deactivated using deleteAt', () => {
const user = {deleteAt: 0} as UserModel;
const user = TestHelper.fakeUserModel({deleteAt: 0});
const result = isDeactivated(user);
expect(result).toBe(false);
});
});
describe('removeUserFromList', () => {
const user1 = {id: 'user1'} as UserProfile;
const user2 = {id: 'user2'} as UserProfile;
const user3 = {id: 'user3'} as UserProfile;
const user1 = TestHelper.fakeUser({id: 'user1'});
const user2 = TestHelper.fakeUser({id: 'user2'});
const user3 = TestHelper.fakeUser({id: 'user3'});
it('should remove the user from the list', () => {
const originalList = [user1, user2, user3];
@ -669,8 +671,8 @@ describe('confirmOutOfOfficeDisabled', () => {
it('should call updateStatus with the correct status when OK is pressed', () => {
confirmOutOfOfficeDisabled(intl, 'online', updateStatus);
const okButton = (Alert.alert as jest.Mock).mock.calls[0][2][1];
okButton.onPress();
const okButton = jest.mocked(Alert.alert).mock.calls[0][2]?.[1];
okButton?.onPress?.();
expect(updateStatus).toHaveBeenCalledWith('online');
});

View file

@ -19,7 +19,25 @@ import DatabaseManager from '@database/manager';
import {prepareCommonSystemValues} from '@queries/servers/system';
import type {APIClientInterface} from '@mattermost/react-native-network-client';
import type {Model, Query} from '@nozbe/watermelondb';
import type {Model, Query, Relation} from '@nozbe/watermelondb';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
import type ChannelBookmarkModel from '@typings/database/models/servers/channel_bookmark';
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
import type DraftModel from '@typings/database/models/servers/draft';
import type FileModel from '@typings/database/models/servers/file';
import type GroupModel from '@typings/database/models/servers/group';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
import type MyTeamModel from '@typings/database/models/servers/my_team';
import type PostModel from '@typings/database/models/servers/post';
import type PostsInChannelModel from '@typings/database/models/servers/posts_in_channel';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type RoleModel from '@typings/database/models/servers/role';
import type TeamModel from '@typings/database/models/servers/team';
import type ThreadModel from '@typings/database/models/servers/thread';
import type UserModel from '@typings/database/models/servers/user';
const DEFAULT_LOCALE = 'en';
@ -247,6 +265,19 @@ class TestHelperSingleton {
};
};
fakeChannelInfo = (overwrite: Partial<ChannelInfo>): ChannelInfo => {
return {
id: this.generateId(),
guest_count: 0,
header: '',
member_count: 0,
purpose: '',
pinned_post_count: 0,
files_count: 0,
...overwrite,
};
};
fakeChannelWithId = (teamId: string): Channel => {
return {
...this.fakeChannel({team_id: teamId}),
@ -319,7 +350,7 @@ class TestHelperSingleton {
return 'success' + this.generateId() + '@simulator.amazonses.com';
};
fakePost = (overwrite: Partial<Post>): Post => {
fakePost = (overwrite?: Partial<Post>): Post => {
const time = Date.now();
return {
@ -424,7 +455,424 @@ class TestHelperSingleton {
};
};
fakeUser = (): UserProfile => {
fakeQuery = <T extends Model>(returnValue: T[]): Query<T> => {
return {
fetch: jest.fn().mockImplementation(async () => returnValue),
observe: jest.fn().mockImplementation(() => of$(returnValue)),
} as unknown as Query<T>;
};
fakeRelation = <T extends Model>(returnValue?: T): Relation<T> => {
return {
fetch: jest.fn().mockImplementation(async () => returnValue!),
_model: {} as T,
_columnName: '',
_relationTableName: '',
_isImmutable: false,
_cachedObservable: of$(returnValue!),
id: this.generateId(),
then: jest.fn(),
set: jest.fn(),
observe: jest.fn().mockImplementation(() => of$(returnValue!)),
};
};
fakeModel = () => {
return {
id: this.generateId(),
_raw: {} as any,
collection: {} as any,
collections: {} as any,
database: {} as any,
db: {} as any,
asModel: {} as any,
_isEditing: false,
_preparedState: 'create' as const,
syncStatus: 'synced' as const,
table: '',
_subscribers: [],
_getChanges: jest.fn(),
update: jest.fn(),
prepareUpdate: jest.fn(),
cancelPrepareUpdate: jest.fn(),
prepareMarkAsDeleted: jest.fn(),
prepareDestroyPermanently: jest.fn(),
markAsDeleted: jest.fn(),
destroyPermanently: jest.fn(),
experimentalMarkAsDeleted: jest.fn(),
experimentalDestroyPermanently: jest.fn(),
observe: jest.fn(),
batch: jest.fn(),
callWriter: jest.fn(),
callReader: jest.fn(),
subAction: jest.fn(),
experimentalSubscribe: jest.fn(),
_notifyChanged: jest.fn(),
_notifyDestroyed: jest.fn(),
_getRaw: jest.fn(),
_setRaw: jest.fn(),
_dangerouslySetRawWithoutMarkingColumnChange: jest.fn(),
__ensureCanSetRaw: jest.fn(),
__ensureNotDisposable: jest.fn(),
};
};
fakeUserModel = (overwrite?: Partial<UserModel>): UserModel => {
const modelBase = this.fakeModel();
return {
...modelBase,
email: this.fakeEmail(),
locale: DEFAULT_LOCALE,
username: this.generateId(),
firstName: this.generateId(),
lastName: this.generateId(),
deleteAt: 0,
roles: 'system_user',
authService: '',
id: this.generateId(),
nickname: '',
notifyProps: this.fakeUserNotifyProps(),
position: '',
updateAt: 0,
isBot: false,
isGuest: false,
lastPictureUpdate: 0,
status: 'offline',
remoteId: null,
props: {},
timezone: {automaticTimezone: 'UTC', manualTimezone: '', useAutomaticTimezone: true},
mentionKeys: [],
userMentionKeys: [],
highlightKeys: [],
termsOfServiceId: '',
termsOfServiceCreateAt: 0,
channelsCreated: this.fakeQuery([]),
channels: this.fakeQuery([]),
posts: this.fakeQuery([]),
preferences: this.fakeQuery([]),
reactions: this.fakeQuery([]),
teams: this.fakeQuery([]),
threadParticipations: this.fakeQuery([]),
prepareStatus: () => null,
...overwrite,
};
};
fakeChannelModel = (overwrite?: Partial<ChannelModel>): ChannelModel => {
return {
...this.fakeModel(),
createAt: 0,
creatorId: this.generateId(),
deleteAt: 0,
updateAt: 0,
displayName: this.generateId(),
isGroupConstrained: false,
name: this.generateId(),
shared: false,
teamId: this.generateId(),
type: 'O' as const,
members: this.fakeQuery([]),
drafts: this.fakeQuery([]),
bookmarks: this.fakeQuery([]),
posts: this.fakeQuery([]),
postsInChannel: this.fakeQuery([]),
team: this.fakeRelation(),
creator: this.fakeRelation(),
info: this.fakeRelation(),
membership: this.fakeRelation(),
categoryChannel: this.fakeRelation(),
toApi: jest.fn(),
...overwrite,
};
};
fakeChannelMembershipModel = (overwrite?: Partial<ChannelMembershipModel>): ChannelMembershipModel => {
return {
...this.fakeModel(),
channelId: this.generateId(),
userId: this.generateId(),
schemeAdmin: false,
memberChannel: this.fakeRelation(),
memberUser: this.fakeRelation(),
getAllChannelsForUser: this.fakeQuery([]),
getAllUsersInChannel: this.fakeQuery([]),
...overwrite,
};
};
fakeMyChannelMembershipModel = (overwrite?: Partial<MyChannelModel>): MyChannelModel => {
return {
...this.fakeModel(),
channel: this.fakeRelation(),
lastPostAt: 0,
lastFetchedAt: 0,
lastViewedAt: 0,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 0,
isUnread: false,
roles: '',
viewedAt: 0,
settings: this.fakeRelation(),
resetPreparedState: jest.fn(),
...overwrite,
};
};
fakeCategoryChannelModel = (overwrite?: Partial<CategoryChannelModel>): CategoryChannelModel => {
return {
...this.fakeModel(),
channel: this.fakeRelation(),
categoryId: this.generateId(),
channelId: this.generateId(),
sortOrder: 0,
category: this.fakeRelation(),
myChannel: this.fakeRelation(),
...overwrite,
};
};
fakeDraftModel = (overwrite?: Partial<DraftModel>): DraftModel => {
return {
...this.fakeModel(),
channelId: this.generateId(),
message: '',
rootId: '',
files: [],
metadata: {},
updateAt: 0,
...overwrite,
};
};
fakePostsInChannelModel = (overwrite?: Partial<PostsInChannelModel>): PostsInChannelModel => {
return {
...this.fakeModel(),
channelId: this.generateId(),
earliest: 0,
latest: 0,
channel: this.fakeRelation(),
...overwrite,
};
};
fakeChannelBookmarkModel = (overwrite?: Partial<ChannelBookmarkModel>): ChannelBookmarkModel => {
return {
...this.fakeModel(),
channelId: this.generateId(),
ownerId: this.generateId(),
fileId: this.generateId(),
displayName: this.generateId(),
createAt: 0,
updateAt: 0,
deleteAt: 0,
sortOrder: 0,
type: 'file',
channel: this.fakeRelation(),
owner: this.fakeRelation(),
file: this.fakeRelation(),
toApi: jest.fn(),
...overwrite,
};
};
fakeFileModel = (overwrite?: Partial<FileModel>): FileModel => {
return {
...this.fakeModel(),
id: this.generateId(),
extension: 'png',
height: 100,
width: 100,
mimeType: 'image/png',
name: 'image1',
size: 100,
imageThumbnail: '',
localPath: 'path/to/image1',
postId: this.generateId(),
post: this.fakeRelation(),
toFileInfo: jest.fn(),
...overwrite,
};
};
fakeMyChannelSettingsModel = (overwrite?: Partial<MyChannelSettingsModel>): MyChannelSettingsModel => {
return {
...this.fakeModel(),
notifyProps: this.fakeChannelNotifyProps(),
myChannel: this.fakeRelation(),
...overwrite,
};
};
fakeTeamModel = (overwrite?: Partial<TeamModel>): TeamModel => {
return {
...this.fakeModel(),
id: this.generateId(),
name: this.generateId(),
displayName: this.generateId(),
type: 'O' as const,
allowedDomains: '',
inviteId: this.generateId(),
description: '',
updateAt: 0,
isAllowOpenInvite: true,
isGroupConstrained: false,
lastTeamIconUpdatedAt: 0,
categories: this.fakeQuery([]),
channels: this.fakeQuery([]),
myTeam: this.fakeRelation(),
teamChannelHistory: this.fakeRelation(),
members: this.fakeQuery([]),
teamSearchHistories: this.fakeQuery([]),
...overwrite,
};
};
fakeChannelInfoModel = (overwrite?: Partial<ChannelInfoModel>): ChannelInfoModel => {
return {
...this.fakeModel(),
guestCount: 0,
header: '',
memberCount: 0,
pinnedPostCount: 0,
filesCount: 0,
purpose: '',
channel: this.fakeRelation(),
...overwrite,
};
};
fakePostModel = (overwrite?: Partial<PostModel>): PostModel => {
return {
...this.fakeModel(),
channelId: this.generateId(),
createAt: 0,
deleteAt: 0,
editAt: 0,
isPinned: false,
message: '',
metadata: {},
originalId: '',
pendingPostId: '',
props: {},
rootId: '',
type: '',
updateAt: 0,
messageSource: '',
previousPostId: '',
userId: this.generateId(),
root: this.fakeQuery([]),
drafts: this.fakeQuery([]),
files: this.fakeQuery([]),
postsInThread: this.fakeQuery([]),
reactions: this.fakeQuery([]),
author: this.fakeRelation(),
channel: this.fakeRelation(),
thread: this.fakeRelation(),
hasReplies: async () => false,
toApi: jest.fn(),
...overwrite,
};
};
fakeGroupModel = (overwrite?: Partial<GroupModel>): GroupModel => {
return {
...this.fakeModel(),
name: this.generateId(),
displayName: this.generateId(),
description: this.generateId(),
source: this.generateId(),
remoteId: this.generateId(),
createdAt: 0,
updatedAt: 0,
deletedAt: 0,
memberCount: 0,
channels: this.fakeQuery([]),
teams: this.fakeQuery([]),
members: this.fakeQuery([]),
...overwrite,
};
};
fakeMyChannelModel = (overwrite?: Partial<MyChannelModel>): MyChannelModel => {
return {
...this.fakeModel(),
lastPostAt: 0,
lastFetchedAt: 0,
lastViewedAt: 0,
manuallyUnread: false,
mentionsCount: 0,
messageCount: 0,
isUnread: false,
roles: '',
viewedAt: 0,
channel: this.fakeRelation(),
settings: this.fakeRelation(),
resetPreparedState: jest.fn(),
...overwrite,
};
};
fakeRoleModel = (overwrite?: Partial<RoleModel>): RoleModel => {
return {
...this.fakeModel(),
name: this.generateId(),
permissions: [],
...overwrite,
};
};
fakeThreadModel = (overwrite?: Partial<ThreadModel>): ThreadModel => {
return {
...this.fakeModel(),
lastReplyAt: 0,
lastFetchedAt: 0,
lastViewedAt: 0,
replyCount: 0,
isFollowing: false,
unreadReplies: 0,
unreadMentions: 0,
viewedAt: 0,
participants: this.fakeQuery([]),
threadsInTeam: this.fakeQuery([]),
post: this.fakeRelation(),
...overwrite,
};
};
fakeMyTeamModel = (overwrite?: Partial<MyTeamModel>): MyTeamModel => {
return {
...this.fakeModel(),
roles: '',
team: this.fakeRelation(),
...overwrite,
};
};
fakePreferenceModel = (overwrite?: Partial<PreferenceModel>): PreferenceModel => {
return {
...this.fakeModel(),
value: '',
category: '',
name: '',
userId: '',
user: this.fakeRelation(),
...overwrite,
};
};
fakeRole = (overwrite?: Partial<Role>): Role => {
return {
id: this.generateId(),
name: this.generateId(),
permissions: [],
...overwrite,
};
};
fakeUser = (overwrite?: Partial<UserProfile>): UserProfile => {
return {
email: this.fakeEmail(),
locale: DEFAULT_LOCALE,
@ -437,13 +885,14 @@ class TestHelperSingleton {
auth_service: '',
id: this.generateId(),
nickname: '',
notify_props: this.fakeNotifyProps(),
notify_props: this.fakeUserNotifyProps(),
position: '',
update_at: 0,
...overwrite,
};
};
fakeNotifyProps = (): UserNotifyProps => {
fakeUserNotifyProps = (overwrite?: Partial<UserNotifyProps>): UserNotifyProps => {
return {
channel: 'false',
comments: 'root',
@ -456,41 +905,23 @@ class TestHelperSingleton {
push: 'default',
push_status: 'away',
calls_desktop_sound: 'true',
calls_mobile_notification_sound: '',
calls_mobile_notification_sound: Ringtone.Calm,
calls_mobile_sound: 'true',
calls_notification_sound: '',
...overwrite,
};
};
fakeUserWithId = (id = this.generateId()): UserProfile => {
fakeChannelNotifyProps = (overwrite?: Partial<ChannelNotifyProps>): ChannelNotifyProps => {
return {
...this.fakeUser(),
id,
create_at: 1507840900004,
update_at: 1507840900004,
delete_at: 0,
auth_service: '',
nickname: '',
notify_props: {
desktop: 'default',
desktop_sound: 'true',
email: 'true',
mark_unread: 'all',
push: 'default',
push_status: 'away',
comments: 'any',
first_name: 'true',
channel: 'true',
mention_keys: '',
highlight_keys: '',
calls_desktop_sound: 'true',
calls_mobile_sound: '',
calls_notification_sound: Ringtone.Calm,
calls_mobile_notification_sound: '',
},
last_picture_update: 0,
position: '',
is_bot: false,
channel_auto_follow_threads: 'on',
desktop: 'default',
email: 'default',
mark_unread: 'all',
push: 'default',
ignore_channel_mentions: 'off',
push_threads: 'mention',
...overwrite,
};
};
@ -589,7 +1020,7 @@ class TestHelperSingleton {
};
initMockEntities = () => {
this.basicUser = this.fakeUserWithId();
this.basicUser = this.fakeUser();
this.basicUser.roles = 'system_user system_admin';
this.basicTeam = this.fakeTeamWithId();
this.basicTeamMember = this.fakeTeamMember(this.basicUser.id, this.basicTeam.id);

13
types/api/posts.d.ts vendored
View file

@ -16,7 +16,18 @@ type PostType =
| 'system_join_leave'
| 'system_leave_channel'
| 'system_purpose_change'
| 'system_remove_from_channel';
| 'system_remove_from_channel'
| 'system_ephemeral_add_to_channel'
| 'system_guest_join_channel'
| 'system_add_guest_to_chan'
| 'system_join_team'
| 'system_leave_team'
| 'system_remove_from_team'
| 'system_combined_user_activity'
| 'add_bot_teams_channels'
| 'system_auto_responder'
| 'custom_calls'
| 'custom_calls_recording';
type PostEmbedType = 'image' | 'message_attachment' | 'opengraph';

View file

@ -43,24 +43,24 @@ export type ServerDatabases = {
[x: string]: ServerDatabase | undefined;
};
export type TransformerArgs = {
export type TransformerArgs<T extends Model, R extends RawValue> = {
action: string;
database: Database;
fieldsMapper?: (model: Model) => void;
tableName?: string;
value: RecordPair;
value: RecordPair<T, R>;
};
export type PrepareBaseRecordArgs = TransformerArgs & {
export type PrepareBaseRecordArgs<T extends Model, R extends RawValue> = TransformerArgs<T, R> & {
fieldsMapper: (model: Model) => void;
}
export type OperationArgs<T extends Model> = {
export type OperationArgs<T extends Model, R extends RawValue> = {
tableName: string;
createRaws?: RecordPair[];
updateRaws?: RecordPair[];
createRaws?: Array<RecordPair<T, R>>;
updateRaws?: Array<RecordPair<T, R>>;
deleteRaws?: T[];
transformer: (args: TransformerArgs) => Promise<T>;
transformer: (args: TransformerArgs<T, R>) => Promise<T>;
};
export type Models = Array<Class<Model>>;
@ -137,9 +137,9 @@ export type SanitizePostsArgs = {
posts: Post[];
};
export type IdenticalRecordArgs = {
existingRecord: Model;
newValue: RawValue;
export type IdenticalRecordArgs<T extends Model, R extends RawValue> = {
existingRecord: T;
newValue: R;
tableName: string;
};
@ -149,21 +149,21 @@ export type RetrieveRecordsArgs = {
condition: Clause;
};
export type ProcessRecordsArgs = {
createOrUpdateRawValues: RawValue[];
deleteRawValues: RawValue[];
export type ProcessRecordsArgs<R extends RawValue> = {
createOrUpdateRawValues: R[];
deleteRawValues: R[];
tableName: string;
fieldName: string;
buildKeyRecordBy?: (obj: Record<string, any>) => string;
shouldUpdate?: (existing: Record<string, any>, newRaw: Record<string, any>) => boolean;
};
export type HandleRecordsArgs<T extends Model> = {
export type HandleRecordsArgs<T extends Model, R extends RawValue> = {
buildKeyRecordBy?: (obj: Record<string, any>) => string;
fieldName: string;
transformer: (args: TransformerArgs) => Promise<T>;
createOrUpdateRawValues: RawValue[];
deleteRawValues?: RawValue[];
transformer: (args: TransformerArgs<T, R>) => Promise<T>;
createOrUpdateRawValues: R[];
deleteRawValues?: R[];
tableName: string;
prepareRecordsOnly: boolean;
shouldUpdate?: (existingRecord: T, newRaw: RawValue) => boolean;
@ -174,9 +174,9 @@ export type RangeOfValueArgs = {
fieldName: string;
};
export type RecordPair = {
record?: Model;
raw: RawValue;
export type RecordPair<T extends Model, R extends RawValue> = {
record?: T;
raw: R;
};
type PrepareOnly = {
@ -317,8 +317,8 @@ export type GetDatabaseConnectionArgs = {
setAsActiveDatabase: boolean;
}
export type ProcessRecordResults<T extends Model> = {
createRaws: RecordPair[];
updateRaws: RecordPair[];
export type ProcessRecordResults<T extends Model, R extends RawValue> = {
createRaws: Array<RecordPair<T, R>>;
updateRaws: Array<RecordPair<T, R>>;
deleteRaws: T[];
}

View file

@ -65,7 +65,7 @@ declare class PostModel extends Model {
rootId: string;
/** type : Type of props (e.g. system message) */
type: string;
type: PostType;
/** user_id : The foreign key of the User who authored this post. */
userId: string;

View file

@ -136,4 +136,5 @@ type RawValue =
| ThreadParticipant
| TeamThreadsSync
| UserProfile
| SlashCommand
| Pick<ChannelMembership, 'channel_id' | 'user_id'>