diff --git a/app/utils/buttonStyles.test.ts b/app/utils/buttonStyles.test.ts new file mode 100644 index 000000000..80836cc78 --- /dev/null +++ b/app/utils/buttonStyles.test.ts @@ -0,0 +1,142 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Preferences from '@constants/preferences'; + +import {buttonBackgroundStyle, buttonStyles, getBackgroundStyles, buttonSizeStyles, buttonTextStyle, buttonTextStyles, buttonTextSizeStyles} from './buttonStyles'; +import {changeOpacity} from './theme'; + +describe('get the style of a button', () => { + const defaultStyle = buttonStyles.main; + const theme = Preferences.THEMES.denim; + const bgStyles = getBackgroundStyles(theme); + + test('button default values', () => { + const tests = [{ + getStyle: () => buttonBackgroundStyle(theme), + expected: [ + defaultStyle, + buttonSizeStyles.m, + bgStyles.primary.default.default, + ], + }, { + getStyle: () => buttonBackgroundStyle(theme, 'xs'), + expected: [ + defaultStyle, + buttonSizeStyles.xs, + bgStyles.primary.default.default, + ], + }, { + getStyle: () => buttonBackgroundStyle(theme, 'lg', 'secondary'), + expected: [ + defaultStyle, + buttonSizeStyles.lg, + bgStyles.secondary.default.default, + ], + }]; + + tests.forEach((t) => { + const style = t.getStyle(); + expect(style).toEqual(t.expected); + }); + }); + + test('button style', () => { + const btnEnphasis: ButtonEmphasis[] = ['primary', 'secondary', 'tertiary', 'link']; + const btnSizes: ButtonSize[] = ['lg', 'm', 's', 'xs']; + const btnTypes: ButtonType[] = ['default', 'destructive', 'disabled', 'inverted']; + const btnState: ButtonState[] = ['active', 'default', 'focus', 'hover']; + + btnEnphasis.forEach((emphasis) => { + btnSizes.forEach((size) => { + btnTypes.forEach((type) => { + btnState.forEach((state) => { + const style = buttonBackgroundStyle(theme, size, emphasis, type, state); + expect(style).toEqual([ + defaultStyle, + buttonSizeStyles[size], + bgStyles[emphasis][type][state], + ]); + }); + }); + }); + }); + }); +}); + +describe('get the text style of a button', () => { + const defaultTextStyle = buttonTextStyles.main; + const theme = Preferences.THEMES.denim; + + test('button text default values', () => { + const tests = [{ + getStyle: () => buttonTextStyle(theme), + expected: [ + defaultTextStyle, + buttonTextSizeStyles.m, + {color: theme.buttonColor}, + ], + }, { + getStyle: () => buttonTextStyle(theme, 'xs'), + expected: [ + defaultTextStyle, + buttonTextSizeStyles.xs, + {color: theme.buttonColor}, + ], + }, { + getStyle: () => buttonTextStyle(theme, 'lg', 'secondary'), + expected: [ + defaultTextStyle, + buttonTextSizeStyles.lg, + {color: theme.buttonBg}, + ], + }]; + + tests.forEach((t) => { + const style = t.getStyle(); + expect(style).toEqual(t.expected); + }); + }); + + test('button style', () => { + const btnEnphasis: ButtonEmphasis[] = ['primary', 'secondary', 'tertiary', 'link']; + const btnSizes: ButtonSize[] = ['lg', 'm', 's', 'xs']; + const btnTypes: ButtonType[] = ['default', 'destructive', 'disabled', 'inverted']; + + const getColor = (type: ButtonType, emphasis: ButtonEmphasis) => { + let color: string = theme.buttonColor; + + if (type === 'disabled') { + color = changeOpacity(theme.centerChannelColor, 0.32); + } + + if ((type === 'destructive' && emphasis !== 'primary')) { + color = theme.errorTextColor; + } + + if ((type === 'inverted' && emphasis === 'primary') || + (type !== 'inverted' && emphasis !== 'primary')) { + color = theme.buttonBg; + } + + if (type === 'inverted' && emphasis === 'tertiary') { + color = theme.sidebarText; + } + + return color; + }; + + btnEnphasis.forEach((emphasis) => { + btnSizes.forEach((size) => { + btnTypes.forEach((type) => { + const style = buttonTextStyle(theme, size, emphasis, type); + expect(style).toEqual([ + defaultTextStyle, + buttonTextSizeStyles[size], + {color: getColor(type, emphasis)}, + ]); + }); + }); + }); + }); +}); diff --git a/app/utils/buttonStyles.ts b/app/utils/buttonStyles.ts index e25049e62..823520e9e 100644 --- a/app/utils/buttonStyles.ts +++ b/app/utils/buttonStyles.ts @@ -5,37 +5,8 @@ import {type StyleProp, StyleSheet, type TextStyle, type ViewStyle} from 'react- import {blendColors, changeOpacity} from '@utils/theme'; -/** - * Returns the appropriate Style object for - * - * - * @param theme - * @param size - * @param emphasis - * @param type - * @param state - * @returns - */ -export const buttonBackgroundStyle = ( - theme: Theme, - size: ButtonSize = 'm', - emphasis: ButtonEmphasis = 'primary', - type: ButtonType = 'default', - state: ButtonState = 'default', -): StyleProp => { - const styles = StyleSheet.create({ - main: { - flex: 0, - alignItems: 'center', - justifyContent: 'center', - borderRadius: 4, - }, - fullWidth: { - width: '100%', - }, - }); - - const backgroundStyles: BackgroundStyles = { +export const getBackgroundStyles = (theme: Theme): BackgroundStyles => { + return { primary: { default: { default: { @@ -313,31 +284,102 @@ export const buttonBackgroundStyle = ( }, }, }; +}; - const sizes: ButtonSizes = StyleSheet.create({ - xs: { - height: 24, - paddingVertical: 6, - paddingHorizontal: 10, - }, - s: { - height: 32, - paddingVertical: 10, - paddingHorizontal: 16, - }, - m: { - height: 40, - paddingVertical: 12, - paddingHorizontal: 20, - }, - lg: { - height: 48, - paddingVertical: 14, - paddingHorizontal: 24, - }, - }); +export const buttonSizeStyles: ButtonSizes = StyleSheet.create({ + xs: { + height: 24, + paddingVertical: 6, + paddingHorizontal: 10, + }, + s: { + height: 32, + paddingVertical: 10, + paddingHorizontal: 16, + }, + m: { + height: 40, + paddingVertical: 12, + paddingHorizontal: 20, + }, + lg: { + height: 48, + paddingVertical: 14, + paddingHorizontal: 24, + }, +}); - return [styles.main, sizes[size], backgroundStyles[emphasis][type][state]]; +export const buttonStyles = StyleSheet.create({ + main: { + flex: 0, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 4, + }, + fullWidth: { + width: '100%', + }, +}); + +export const buttonTextStyles = StyleSheet.create({ + main: { + fontFamily: 'OpenSans-SemiBold', + fontWeight: '600', + textAlignVertical: 'center', + alignItems: 'center', + justifyContent: 'center', + padding: 1, + }, + underline: { + textDecorationLine: 'underline', + }, +}); + +export const buttonTextSizeStyles = StyleSheet.create({ + xs: { + fontSize: 11, + lineHeight: 10, + letterSpacing: 0.02, + marginTop: 2, + }, + s: { + fontSize: 12, + lineHeight: 12, + marginTop: 1, + }, + m: { + fontSize: 14, + lineHeight: 14, + marginTop: 3, + }, + lg: { + fontSize: 16, + lineHeight: 18, + marginTop: 1, + }, +}); + +/** + * Returns the appropriate Style object for + * + * + * @param theme + * @param size + * @param emphasis + * @param type + * @param state + * @returns + */ +export const buttonBackgroundStyle = ( + theme: Theme, + size: ButtonSize = 'm', + emphasis: ButtonEmphasis = 'primary', + type: ButtonType = 'default', + state: ButtonState = 'default', +): StyleProp => { + const backgroundStyles = getBackgroundStyles(theme); + + return [buttonStyles.main, buttonSizeStyles[size], backgroundStyles[emphasis][type][state]]; }; /** @@ -376,43 +418,5 @@ export const buttonTextStyle = ( color = theme.sidebarText; } - const styles = StyleSheet.create({ - main: { - fontFamily: 'OpenSans-SemiBold', - fontWeight: '600', - textAlignVertical: 'center', - alignItems: 'center', - justifyContent: 'center', - padding: 1, - }, - underline: { - textDecorationLine: 'underline', - }, - }); - - const sizes = StyleSheet.create({ - xs: { - fontSize: 11, - lineHeight: 10, - letterSpacing: 0.02, - marginTop: 2, - }, - s: { - fontSize: 12, - lineHeight: 12, - marginTop: 1, - }, - m: { - fontSize: 14, - lineHeight: 14, - marginTop: 3, - }, - lg: { - fontSize: 16, - lineHeight: 18, - marginTop: 1, - }, - }); - - return [styles.main, sizes[size], {color}]; + return [buttonTextStyles.main, buttonTextSizeStyles[size], {color}]; }; diff --git a/app/utils/categories.test.ts b/app/utils/categories.test.ts new file mode 100644 index 000000000..4a6bfd1b5 --- /dev/null +++ b/app/utils/categories.test.ts @@ -0,0 +1,663 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +/* eslint-disable max-lines */ + +import {Preferences} from '@constants'; +import DatabaseManager from '@database/manager'; + +import { + makeCategoryChannelId, + isUnreadChannel, + filterArchivedChannels, + filterAutoclosedDMs, + filterManuallyClosedDms, + sortChannels, + getUnreadIds, + type ChannelWithMyChannel, +} 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', () => { + const serverUrl = 'baseHandler.test.com'; + + const buildChannelsData = () => { + const dt1 = Date.now(); + const dt2 = Date.now() - (24 * 60 * 60 * 1000); + const dt3 = Date.now() - (2 * 24 * 60 * 60 * 1000); + const dt4 = Date.now() - (3 * 24 * 60 * 60 * 1000); + + const channels: Channel[] = [{ + id: 'dm1', + name: 'me__other1', + display_name: 'DM 1', + type: 'D', + create_at: dt2, + update_at: dt2, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt2, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }, { + id: 'dm2', + name: 'me__other2', + display_name: 'Zandoval 2', + type: 'D', + create_at: dt4, + update_at: dt4, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt4, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }, { + id: 'open-channel', + name: 'open-channel', + display_name: 'Open Channel', + type: 'O', + create_at: dt1, + update_at: dt1, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt1, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }, { + id: 'gm1', + name: 'the_gm_channel_name', + display_name: 'Mario, Persona 2, Persona 3', + type: 'G', + create_at: dt3, + update_at: dt3, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt3, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }]; + const myChannels: ChannelMembership[] = [ + { + id: 'dm1', + user_id: 'me', + channel_id: 'dm1', + last_post_at: dt2, + last_viewed_at: dt2, + last_update_at: dt2, + mention_count: 0, + msg_count: 20, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + { + id: 'dm2', + user_id: 'me', + channel_id: 'dm2', + last_post_at: dt4, + last_viewed_at: dt4, + last_update_at: dt4, + mention_count: 3, + msg_count: 23, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, { + id: 'open-channel', + user_id: 'me', + channel_id: 'open-channel', + last_post_at: dt1, + last_viewed_at: dt1, + last_update_at: dt1, + mention_count: 1, + msg_count: 21, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'all', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, { + id: 'gm1', + user_id: 'me', + channel_id: 'gm1', + last_post_at: dt3, + last_viewed_at: dt3, + last_update_at: dt3, + mention_count: 0, + msg_count: 0, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + ]; + + return {channels, myChannels}; + }; + + const createChannelsDbRecords = async (db: ServerDatabase, channels: Channel[], myChannels: ChannelMembership[]): Promise => { + 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]; + return { + channel, + myChannel, + sortOrder: index, + }; + }); + }; + + beforeEach(async () => { + await DatabaseManager.init([serverUrl]); + }); + + afterEach(() => { + DatabaseManager.destroyServerDatabase(serverUrl); + }); + + test('makeCategoryChannelId', () => { + const teamId = 'my-team-id'; + const channelId = 'my-channel-id'; + const category = makeCategoryChannelId(teamId, channelId); + expect(category).toBe(`${teamId}_${channelId}`); + }); + + test('isUnreadChannel', async () => { + const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + + const channels: Channel[] = [{ + id: 'c', + name: 'channel', + display_name: 'Channel', + type: 'O', + create_at: 1, + update_at: 1, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: 2, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }]; + const myChannels: ChannelMembership[] = [ + { + id: 'c', + user_id: 'me', + channel_id: 'c', + last_post_at: 1617311494451, + last_viewed_at: 1617311494451, + last_update_at: 1617311494451, + mention_count: 3, + msg_count: 10, + roles: 'guest', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + ]; + + const models = await db.operator.handleMyChannel({channels, myChannels, isCRTEnabled: false, prepareRecordsOnly: false}); + expect(models).toHaveLength(1); + const myChannel = models[0]; + let isUnread = isUnreadChannel(myChannel); + expect(isUnread).toBe(true); + await db.database.write(async () => { + await myChannel.update((record) => { + record.mentionsCount = 0; + }); + }); + + isUnread = isUnreadChannel(myChannel); + expect(isUnread).toBe(true); + + const notifyProps: Partial = {mark_unread: 'mention'}; + isUnread = isUnreadChannel(myChannel, notifyProps); + expect(isUnread).toBe(false); + + isUnread = isUnreadChannel(myChannel, notifyProps, myChannel.id); + expect(isUnread).toBe(true); + }); + + test('filterArchivedChannels', async () => { + const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const channels: Channel[] = [{ + id: 'channel_1', + name: 'channel_1', + display_name: 'Channel 1', + type: 'O', + create_at: 1, + update_at: 1, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: 2, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }, { + id: 'channel_2', + name: 'channel_2', + display_name: 'Channel 2', + type: 'O', + create_at: 1, + update_at: 1, + delete_at: 123, + team_id: '123', + header: '', + purpose: '', + last_post_at: 2, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }]; + const myChannels: ChannelMembership[] = [ + { + id: 'channel_1', + user_id: 'me', + channel_id: 'channel_1', + last_post_at: 1617311494451, + last_viewed_at: 1617311494451, + last_update_at: 1617311494451, + mention_count: 3, + msg_count: 10, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + { + id: 'channel_2', + user_id: 'me', + channel_id: 'channel_2', + last_post_at: 1617311494451, + last_viewed_at: 1617311494451, + last_update_at: 1617311494451, + mention_count: 3, + msg_count: 10, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + ]; + + const channelModels = await db.operator.handleChannel({channels, prepareRecordsOnly: false}); + const myChannelModels = await db.operator.handleMyChannel({channels, myChannels, prepareRecordsOnly: false}); + const data: ChannelWithMyChannel[] = [{ + channel: channelModels[0], + myChannel: myChannelModels[0], + sortOrder: 0, + }, { + channel: channelModels[1], + myChannel: myChannelModels[1], + sortOrder: 1, + }]; + let archived = filterArchivedChannels(data, ''); + expect(archived).toHaveLength(1); + + archived = filterArchivedChannels(data, channels[1].id); + expect(archived).toHaveLength(2); + }); + + test('filterAutoclosedDMs', async () => { + const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + + let result = filterAutoclosedDMs('channels', 3, 'me', '', [], [], {}); + expect(result).toEqual([]); + + 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 channels: Channel[] = [{ + id: 'dm1', + name: 'me__other1', + display_name: 'DM 1', + type: 'D', + create_at: 1, + update_at: 1, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt_chan1, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }, { + id: 'dm2', + name: 'me__other2', + display_name: 'DM 2', + type: 'D', + create_at: 1, + update_at: 1, + delete_at: 0, + team_id: '123', + header: '', + purpose: '', + last_post_at: dt_chan2, + creator_id: 'me', + total_msg_count: 20, + extra_update_at: 0, + shared: false, + scheme_id: null, + group_constrained: false, + }]; + const myChannels: ChannelMembership[] = [ + { + id: 'dm1', + user_id: 'me', + channel_id: 'dm1', + last_post_at: dt_chan1, + last_viewed_at: dt_chan1, + last_update_at: dt_chan1, + mention_count: 0, + msg_count: 20, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + { + id: 'dm2', + user_id: 'me', + channel_id: 'dm2', + last_post_at: dt_chan2, + last_viewed_at: dt_chan2, + last_update_at: dt_chan2, + mention_count: 3, + msg_count: 10, + roles: 'user', + notify_props: { + desktop: 'default', + email: 'default', + mark_unread: 'mention', + push: 'mention', + ignore_channel_mentions: 'default', + }, + }, + ]; + + const channelModels = await db.operator.handleChannel({channels, prepareRecordsOnly: false}); + const myChannelModels = await db.operator.handleMyChannel({channels, myChannels, prepareRecordsOnly: false}); + const data: ChannelWithMyChannel[] = [{ + channel: channelModels[0], + myChannel: myChannelModels[0], + sortOrder: 0, + }, { + channel: channelModels[1], + myChannel: myChannelModels[1], + sortOrder: 0, + }]; + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', data, autoclosePrefs, {}); + expect(result.length).toEqual(2); + + result = filterAutoclosedDMs('direct_messages', 3, 'me', 'dm1', data, autoclosePrefs, {}); + expect(result.length).toEqual(2); + + result = filterAutoclosedDMs('direct_messages', 3, 'me', 'dm2', data, autoclosePrefs, {}); + expect(result.length).toEqual(2); + + const lastViewedData: ChannelWithMyChannel[] = [{ + ...data[0], + myChannel: { + ...data[0].myChannel, + lastViewedAt: 0, + } as MyChannelModel, + }, data[1]]; + + const autoclosePrefsNotViewed = [autoclosePrefs[1]]; + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', lastViewedData, autoclosePrefsNotViewed, {}); + expect(result.length).toEqual(1); + + const deactivated = new Map(); + deactivated.set('other1', { + id: 'other1', + deleteAt: dt_chan1 + 1, + } as UserModel); + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', data, autoclosePrefs, {}, deactivated); + expect(result.length).toEqual(1); + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', data, autoclosePrefs, {}, undefined, 'dm2'); + expect(result[0].channel.id).toEqual('dm2'); + + const mockNotUnread: ChannelWithMyChannel[] = [data[0], { + ...data[1], + myChannel: { + 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: { + id: data[0].myChannel.id, + lastViewedAt: 2, + isUnread: false, + } as MyChannelModel, + }, { + ...data[1], + myChannel: { + id: data[1].myChannel.id, + lastViewedAt: 1, + isUnread: false, + } as MyChannelModel, + }]; + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockRecent1, [], {}); + expect(result[0].channel.id).toEqual('dm1'); + + const mockRecent2: ChannelWithMyChannel[] = [{ + ...data[0], + myChannel: { + id: data[0].myChannel.id, + lastViewedAt: 1, + isUnread: false, + } as MyChannelModel, + }, { + ...data[1], + myChannel: { + id: data[1].myChannel.id, + lastViewedAt: 2, + isUnread: false, + } as MyChannelModel, + }]; + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockRecent2, [], {}); + expect(result[0].channel.id).toEqual('dm2'); + + const mockSameLastViewed: ChannelWithMyChannel[] = [{ + ...data[0], + myChannel: { + id: data[0].myChannel.id, + lastViewedAt: 1, + isUnread: false, + } as MyChannelModel, + }, { + ...data[1], + myChannel: { + id: data[1].myChannel.id, + lastViewedAt: 1, + isUnread: false, + } as MyChannelModel, + }]; + + result = filterAutoclosedDMs('direct_messages', 3, 'me', '', mockSameLastViewed, [], {}); + expect(result[0].channel.id).toEqual('dm1'); + }); + + 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 {channels, myChannels} = buildChannelsData(); + const data = await createChannelsDbRecords(db, channels, myChannels); + + const notifyProps = myChannels.reduce>>((obj, m) => { + obj[m.channel_id] = m.notify_props; + return obj; + }, {}); + + const result = filterManuallyClosedDms(data, notifyProps, closedDMsPrefs, 'me'); + expect(result.length).toEqual(3); + }); + + test('sortChannels', async () => { + const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + + const {channels, myChannels} = buildChannelsData(); + const data = await createChannelsDbRecords(db, channels, myChannels); + + const notifyProps = myChannels.reduce>>((obj, m) => { + obj[m.channel_id] = m.notify_props; + return obj; + }, {}); + + let result = sortChannels('recent', data, notifyProps, 'en'); + const recentIds = result.map((r) => r.id); + expect(recentIds).toEqual(['open-channel', 'dm1', 'gm1', 'dm2']); + + result = sortChannels('manual', data, notifyProps, 'en'); + const manualIds = result.map((r) => r.id); + expect(manualIds).toEqual(['dm1', 'dm2', 'open-channel', 'gm1']); + + result = sortChannels('alpha', data, notifyProps, 'en'); + const alphaIds = result.map((r) => r.id); + expect(alphaIds).toEqual(['open-channel', 'dm1', 'gm1', 'dm2']); + }); + + test('getUnreadIds', async () => { + const db = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + + const {channels, myChannels} = buildChannelsData(); + const data = await createChannelsDbRecords(db, channels, myChannels); + + const notifyProps = myChannels.reduce>>((obj, m) => { + obj[m.channel_id] = m.notify_props; + return obj; + }, {}); + + let result = getUnreadIds(data, notifyProps); + let ids = Array.from(result); + expect(ids).toEqual(['dm2', 'open-channel']); + + result = getUnreadIds(data, notifyProps, 'dm1'); + ids = Array.from(result); + expect(ids).toEqual(['dm1', 'dm2', 'open-channel']); + }); +}); diff --git a/app/utils/categories.ts b/app/utils/categories.ts index 91f9afadc..810a902ce 100644 --- a/app/utils/categories.ts +++ b/app/utils/categories.ts @@ -24,7 +24,7 @@ export function makeCategoryChannelId(teamId: string, channelId: string) { export const isUnreadChannel = (myChannel: MyChannelModel, notifyProps?: Partial, lastUnreadChannelId?: string) => { const isMuted = notifyProps?.mark_unread === General.MENTION; - return myChannel.mentionsCount || (!isMuted && myChannel.isUnread) || (myChannel.id === lastUnreadChannelId); + return Boolean(myChannel.mentionsCount) || (!isMuted && myChannel.isUnread) || (myChannel.id === lastUnreadChannelId); }; export const filterArchivedChannels = (channelsWithMyChannel: ChannelWithMyChannel[], currentChannelId: string) => {