Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MM-60405] Crossteam search #8411

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions app/components/team_list/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';

import {fireEvent, renderWithEverything} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';

import TeamList from './index';

import type Database from '@nozbe/watermelondb/Database';
import type TeamModel from '@typings/database/models/servers/team';

describe('TeamList', () => {
let database: Database;
beforeAll(async () => {
const server = await TestHelper.setupServerDatabase();
database = server.database;
});
const teams = [
{id: 'team1', displayName: 'Team 1'} as TeamModel,
{id: 'team2', displayName: 'Team 2'} as TeamModel,
];
beforeAll(async () => {
const server = await TestHelper.setupServerDatabase();
database = server.database;
});

it('should call onPress when a team is pressed', () => {
const onPress = jest.fn();
const {getByText} = renderWithEverything(
<TeamList
teams={teams}
onPress={onPress}
/>,
{database},
);
fireEvent.press(getByText('Team 1'));
expect(onPress).toHaveBeenCalledWith('team1');
});

it('should render loading component when loading is true', () => {
const {getByTestId} = renderWithEverything(
<TeamList
teams={teams}
onPress={jest.fn()}
loading={true}
/>,
{database},
);
expect(getByTestId('team_list.loading')).toBeTruthy();
});

it('should render separator after the first item when separatorAfterFirstItem is true', () => {
const {getByTestId} = renderWithEverything(
<TeamList
teams={teams}
onPress={jest.fn()}
separatorAfterFirstItem={true}
/>,
{database},
);
expect(getByTestId('team_list.separator')).toBeTruthy();
});
});
27 changes: 23 additions & 4 deletions app/components/team_list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type Props = {
testID?: string;
textColor?: string;
type?: BottomSheetList;
hideIcon?: boolean;
separatorAfterFirstItem?: boolean;
}

const styles = StyleSheet.create({
Expand All @@ -32,6 +34,10 @@ const styles = StyleSheet.create({
contentContainer: {
marginBottom: 4,
},
separator: {
height: 1,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
});

const keyExtractor = (item: TeamModel) => item.id;
Expand All @@ -47,25 +53,38 @@ export default function TeamList({
testID,
textColor,
type = 'FlatList',
hideIcon = false,
separatorAfterFirstItem = false,
}: Props) {
const List = useMemo(() => (type === 'FlatList' ? FlatList : BottomSheetFlatList), [type]);

const renderTeam = useCallback(({item: t}: ListRenderItemInfo<Team|TeamModel>) => {
return (
const renderTeam = useCallback(({item: t, index: i}: ListRenderItemInfo<Team|TeamModel>) => {
let teamListItem = (
<TeamListItem
onPress={onPress}
team={t}
textColor={textColor}
iconBackgroundColor={iconBackgroundColor}
iconTextColor={iconTextColor}
selectedTeamId={selectedTeamId}
hideIcon={hideIcon}
/>
);
}, [textColor, iconTextColor, iconBackgroundColor, onPress, selectedTeamId]);
if (separatorAfterFirstItem && i === 0) {
teamListItem = (<>
{teamListItem}
<View
style={styles.separator}
testID='team_list.separator'
/>
</>);
}
return teamListItem;
}, [textColor, iconTextColor, iconBackgroundColor, onPress, selectedTeamId, hideIcon, separatorAfterFirstItem]);

let footer;
if (loading) {
footer = (<Loading/>);
footer = (<Loading testID='team_list.loading'/>);
}

return (
Expand Down
57 changes: 57 additions & 0 deletions app/components/team_list/team_list_item/team_list_item.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';

import {renderWithIntlAndTheme, fireEvent} from '@test/intl-test-helper';

import TeamListItem from './team_list_item';

import type TeamModel from '@typings/database/models/servers/team';

describe('TeamListItem', () => {
const team = {
id: 'team_id',
displayName: 'Team Display Name',
lastTeamIconUpdatedAt: 0,
} as TeamModel;
const iconTestId = `team_sidebar.team_list.team_list_item.${team.id}.team_icon`;

it('should call onPress when pressed', () => {
const onPressMock = jest.fn();
const {getByText} = renderWithIntlAndTheme(
<TeamListItem
team={team}
onPress={onPressMock}
/>,
);

fireEvent.press(getByText('Team Display Name'));

expect(onPressMock).toHaveBeenCalledWith('team_id');
});

it('should render TeamIcon when hideIcon is false', () => {
const {getByTestId} = renderWithIntlAndTheme(
<TeamListItem
team={team}
onPress={jest.fn()}
hideIcon={false}
/>,
);

expect(getByTestId(iconTestId)).toBeTruthy();
});

it('should not render TeamIcon when hideIcon is true', () => {
const {queryByTestId} = renderWithIntlAndTheme(
<TeamListItem
team={team}
onPress={jest.fn()}
hideIcon={true}
/>,
);

expect(queryByTestId(iconTestId)).toBeNull();
});
});
27 changes: 15 additions & 12 deletions app/components/team_list/team_list_item/team_list_item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Props = {
iconBackgroundColor?: string;
selectedTeamId?: string;
onPress: (teamId: string) => void;
hideIcon?: boolean;
}

export const ITEM_HEIGHT = 56;
Expand Down Expand Up @@ -50,7 +51,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});

export default function TeamListItem({team, textColor, iconTextColor, iconBackgroundColor, selectedTeamId, onPress}: Props) {
export default function TeamListItem({team, textColor, iconTextColor, iconBackgroundColor, selectedTeamId, onPress, hideIcon}: Props) {
const theme = useTheme();
const styles = getStyleSheet(theme);

Expand All @@ -68,17 +69,19 @@ export default function TeamListItem({team, textColor, iconTextColor, iconBackgr
type='opacity'
style={styles.touchable}
>
<View style={styles.icon_container}>
<TeamIcon
id={team.id}
displayName={displayName}
lastIconUpdate={lastTeamIconUpdateAt}
selected={false}
textColor={iconTextColor || theme.centerChannelColor}
backgroundColor={iconBackgroundColor || changeOpacity(theme.centerChannelColor, 0.16)}
testID={`${teamListItemTestId}.team_icon`}
/>
</View>
{!hideIcon &&
<View style={styles.icon_container}>
<TeamIcon
id={team.id}
displayName={displayName}
lastIconUpdate={lastTeamIconUpdateAt}
selected={false}
textColor={iconTextColor || theme.centerChannelColor}
backgroundColor={iconBackgroundColor || changeOpacity(theme.centerChannelColor, 0.16)}
testID={`${teamListItemTestId}.team_icon`}
/>
</View>
}
<Text
style={[styles.text, Boolean(textColor) && {color: textColor}]}
numberOfLines={1}
Expand Down
12 changes: 11 additions & 1 deletion app/screens/home/search/bottom_sheet_team_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
// See LICENSE.txt for license information.

import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';

import TeamList from '@components/team_list';
import {useIsTablet} from '@hooks/device';
import BottomSheetContent from '@screens/bottom_sheet/content';
import {dismissBottomSheet} from '@screens/navigation';

import {ALL_TEAMS_ID} from '.';

import type TeamModel from '@typings/database/models/servers/team';

type Props = {
Expand All @@ -18,6 +21,7 @@ type Props = {
}

export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: Props) {
const intl = useIntl();
const isTablet = useIsTablet();
const showTitle = !isTablet && Boolean(teams.length);

Expand All @@ -26,6 +30,10 @@ export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: P
dismissBottomSheet();
}, [setTeamId]);

// teamList is a copy of teams to avoid modifying the original array
const teamList = [...teams];
teamList.unshift({id: ALL_TEAMS_ID, displayName: intl.formatMessage({id: 'mobile.search.team.all_teams', defaultMessage: 'All teams'})} as TeamModel);

return (
<BottomSheetContent
showButton={false}
Expand All @@ -35,10 +43,12 @@ export default function BottomSheetTeamList({teams, title, setTeamId, teamId}: P
>
<TeamList
selectedTeamId={teamId}
teams={teams}
teams={teamList}
onPress={onPress}
testID='search.select_team_slide_up.team_list'
type={isTablet ? 'FlatList' : 'BottomSheetFlatList'}
hideIcon={true}
separatorAfterFirstItem={true}
/>
</BottomSheetContent>
);
Expand Down
2 changes: 2 additions & 0 deletions app/screens/home/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const enhance = withObservables([], ({database}: WithDatabaseArgs) => {
};
});

export const ALL_TEAMS_ID = '';

export default compose(
withDatabase,
enhance,
Expand Down
62 changes: 62 additions & 0 deletions app/screens/home/search/initial/modifiers/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {useSharedValue} from 'react-native-reanimated';

import TeamPicker from '@screens/home/search/team_picker';
import {renderWithIntlAndTheme} from '@test/intl-test-helper';

import {ALL_TEAMS_ID} from '../..';

import Modifiers from './index';

import type {SearchRef} from '@components/search';
import type {TeamModel} from '@database/models/server';

jest.mock('@screens/home/search/team_picker', () => jest.fn(() => null));
jest.mock('./show_more', () => jest.fn(() => null));
jest.mock('@react-native-camera-roll/camera-roll', () => jest.fn());

describe('Modifiers', () => {
const scrollEnabled = useSharedValue(true);
const searchRef = React.createRef<SearchRef>();
const setSearchValue = jest.fn();
const setTeamId = jest.fn();
const teams = [{id: 'team1', displayName: 'Team 1'}, {id: 'team2', displayName: 'Team 2'}] as TeamModel[];

const renderComponent = (teamId = ALL_TEAMS_ID) => {
return renderWithIntlAndTheme(
<Modifiers
setSearchValue={setSearchValue}
setTeamId={setTeamId}
teamId={teamId}
teams={teams}
scrollEnabled={scrollEnabled}
searchRef={searchRef}
/>,
);
};

it('should render correctly', () => {
const {getByTestId} = renderComponent();
expect(getByTestId('search.modifier.header')).toBeTruthy();
});

it('should render TeamPicker when there are multiple teams', () => {
renderComponent();
expect(TeamPicker).toHaveBeenCalled();
});

it('should render the From: and In: modifiers when a team is selected', () => {
const {getByTestId} = renderComponent('team1');
expect(getByTestId('search.modifier.from')).toBeTruthy();
expect(getByTestId('search.modifier.in')).toBeTruthy();
});

it('should not render the From: and In: modifiers when all teams are selected', () => {
const {queryByTestId} = renderComponent(ALL_TEAMS_ID);
expect(queryByTestId('search.modifier.from')).toBeNull();
expect(queryByTestId('search.modifier.in')).toBeNull();
});
});
Loading