diff --git a/app/components/team_sidebar/team_list/index.test.tsx b/app/components/team_sidebar/team_list/index.test.tsx new file mode 100644 index 00000000000..fb13a3ffa32 --- /dev/null +++ b/app/components/team_sidebar/team_list/index.test.tsx @@ -0,0 +1,243 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {render, waitFor} from '@testing-library/react-native'; +import React from 'react'; +import {of as of$} from 'rxjs'; + +import TeamList from './team_list'; + +import TeamListWrapper from './'; + +// Mock database queries +jest.mock('@queries/servers/team', () => { + const {of} = require('rxjs'); + return { + queryMyTeams: jest.fn(() => of([])), + queryJoinedTeams: jest.fn(() => ({ + observe: jest.fn(() => of([])), + })), + }; +}); + +jest.mock('@queries/servers/preference', () => { + const {of} = require('rxjs'); + return { + queryPreferencesByCategoryAndName: jest.fn(() => ({ + observe: jest.fn(() => of([])), + observeWithColumns: jest.fn(() => of([])), + })), + }; +}); + +const {queryPreferencesByCategoryAndName} = require('@queries/servers/preference'); +const {queryMyTeams, queryJoinedTeams} = require('@queries/servers/team'); + +// Mock WatermelonDB HOCs +jest.mock('@nozbe/watermelondb/react', () => ({ + withDatabase: jest.fn((Component) => Component), + withObservables: jest.fn((_, observableMapper) => { + // eslint-disable-next-line react/display-name + return (Component: any) => (props: any) => { + const observables = observableMapper(props); + const mockedProps: Record = {}; + // eslint-disable-next-line guard-for-in + for (const key in observables) { + observables[key].subscribe((value: any) => { + mockedProps[key] = value; + }); + } + return ( + ); + }; + }), +})); + +jest.mock('./team_list', () => { + return jest.fn(() => null); // Mock implementation for TeamList +}); + +describe('withTeams HOC', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should handle valid data correctly', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([{id: '1', roles: 'team_user'}])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', displayName: 'Team 1'}, + {id: '2', displayName: 'Team 2'}, + ])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([{value: '1,2'}])), + }); + + render(); + + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [{id: '1', roles: 'team_user'}], + }), + expect.anything(), + ); + }); + }); + + it('should handle missing team IDs gracefully', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([{id: '1', roles: 'team_user'}])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', displayName: 'Team 1'}, + {id: undefined, displayName: 'Team 2'}, + ])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([{value: '1,2'}])), + }); + + render(); + + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [{id: '1', roles: 'team_user'}], + }), + expect.anything(), + ); + }); + }); + + it('should handle empty team order preferences gracefully', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([{id: '1', roles: 'team_user'}])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', displayName: 'Team 1'}, + {id: '2', displayName: 'Team 2'}, + ])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([])), + }); + + render(); + + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [{id: '1', roles: 'team_user'}], + }), + expect.anything(), + ); + }); + }); + + it('should handle undefined in team order preferences gracefully', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', roles: 'team_user'}, + {id: '2', roles: 'team_user'}, + ])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', displayName: 'Team 1'}, + {id: '2', displayName: 'Team 2'}, + ])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([{value: '1,,2'}])), + }); + + render(); + + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [ + {id: '1', roles: 'team_user'}, + {id: '2', roles: 'team_user'}, + ], + }), + expect.anything(), + ); + }); + }); + + it('should handle wrong data in team order preferences gracefully', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', roles: 'team_user'}, + {id: '2', roles: 'team_user'}, + ])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([ + {id: '1', displayName: 'Team 1'}, + {id: '2', displayName: 'Team 2'}, + ])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([{value: '1,undefined,2'}])), + }); + + render(); + + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [ + {id: '1', roles: 'team_user'}, + {id: '2', roles: 'team_user'}, + ], + }), + expect.anything(), + ); + }); + }); + + it('should handle empty teams list gracefully', async () => { + queryMyTeams.mockReturnValue({ + observe: jest.fn(() => of$([])), + }); + + queryJoinedTeams.mockReturnValue({ + observe: jest.fn(() => of$([])), + }); + + queryPreferencesByCategoryAndName.mockReturnValue({ + observeWithColumns: jest.fn(() => of$([])), + }); + + render(); + await waitFor(() => { + expect(TeamList).toHaveBeenCalledWith( + expect.objectContaining({ + myOrderedTeams: [], + }), + expect.anything(), + ); + }); + }); +}); diff --git a/app/components/team_sidebar/team_list/index.ts b/app/components/team_sidebar/team_list/index.ts index 117c9c9c8db..17461f34f6a 100644 --- a/app/components/team_sidebar/team_list/index.ts +++ b/app/components/team_sidebar/team_list/index.ts @@ -14,12 +14,6 @@ import {queryJoinedTeams, queryMyTeams} from '@queries/servers/team'; import TeamList from './team_list'; import type {WithDatabaseArgs} from '@typings/database/database'; -import type MyTeamModel from '@typings/database/models/servers/my_team'; - -interface TeamWithLowerName { - myTeam: MyTeamModel; - lowerName?: string; -} const withTeams = withObservables([], ({database}: WithDatabaseArgs) => { const myTeams = queryMyTeams(database).observe(); @@ -37,35 +31,21 @@ const withTeams = withObservables([], ({database}: WithDatabaseArgs) => { if (sortedTeamIds.size) { const mySortedTeams = [...sortedTeamIds]. - filter((id) => membershipMap.has(id)). + filter((id) => id && membershipMap.has(id)). map((id) => membershipMap.get(id)!); const extraTeams = teams. - filter((t) => !sortedTeamIds.has(t.id) && membershipMap.has(t.id)). - map((t) => ({ - myTeam: membershipMap.get(t.id)!, - lowerName: t.displayName.toLocaleLowerCase(), - } as TeamWithLowerName)). - sort((a, b) => a.lowerName!.localeCompare(b.lowerName!)). - map((t) => { - delete t.lowerName; - return t; - }); + filter((t) => t.id && !sortedTeamIds.has(t.id) && membershipMap.has(t.id)). + sort((a, b) => a.displayName.toLocaleLowerCase().localeCompare(b.displayName.toLocaleLowerCase())). + map((t) => membershipMap.get(t.id)!); return [...mySortedTeams, ...extraTeams]; } return teams. - filter((t) => membershipMap.has(t.id)). - map((t) => ({ - myTeam: membershipMap.get(t.id)!, - lowerName: t.displayName.toLocaleLowerCase(), - } as TeamWithLowerName)). - sort((a, b) => a.lowerName!.localeCompare(b.lowerName!)). - map((t) => { - delete t.lowerName; - return t.myTeam; - }); + filter((t) => t.id && membershipMap.has(t.id)). + sort((a, b) => a.displayName.toLocaleLowerCase().localeCompare(b.displayName.toLocaleLowerCase())). + map((t) => membershipMap.get(t.id)!); }), ); return {