Skip to content

Commit

Permalink
Ensure myTeam.id is not undefined (#8375)
Browse files Browse the repository at this point in the history
  • Loading branch information
enahum authored Nov 26, 2024
1 parent 50a7b14 commit 7123c7f
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 27 deletions.
243 changes: 243 additions & 0 deletions app/components/team_sidebar/team_list/index.test.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any> = {};
// eslint-disable-next-line guard-for-in
for (const key in observables) {
observables[key].subscribe((value: any) => {
mockedProps[key] = value;
});
}
return (
<Component
{...props}
{...mockedProps}
/>);
};
}),
}));

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(<TeamListWrapper/>);

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(<TeamListWrapper/>);

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(<TeamListWrapper/>);

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(<TeamListWrapper/>);

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(<TeamListWrapper/>);

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(<TeamListWrapper/>);
await waitFor(() => {
expect(TeamList).toHaveBeenCalledWith(
expect.objectContaining({
myOrderedTeams: [],
}),
expect.anything(),
);
});
});
});
34 changes: 7 additions & 27 deletions app/components/team_sidebar/team_list/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 {
Expand Down

0 comments on commit 7123c7f

Please sign in to comment.