Skip to content

Commit

Permalink
Add more tests and fix issues related to OSM Teams
Browse files Browse the repository at this point in the history
  • Loading branch information
willemarcel committed Jul 6, 2023
1 parent fcda1e3 commit acfdf31
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 83 deletions.
7 changes: 4 additions & 3 deletions backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def create_app(env="backend.config.EnvironmentConfig"):
env = "backend.config.TestEnvironmentConfig"
app.config.from_object(env)
# Enable logging to files
# initialise_logger(app)
initialise_logger(app)
app.logger.info("Starting up a new Tasking Manager application")

# Connect to database
Expand Down Expand Up @@ -380,7 +380,7 @@ def add_api_endpoints(app):
SystemAuthenticationLoginAPI,
SystemAuthenticationCallbackAPI,
OSMTeamsAuthenticationCallbackAPI,
OSMTeamsAuthenticationAPI
OSMTeamsAuthenticationAPI,
)
from backend.api.system.applications import SystemApplicationsRestAPI
from backend.api.system.image_upload import SystemImageUploadRestAPI
Expand Down Expand Up @@ -936,7 +936,8 @@ def add_api_endpoints(app):
SystemAuthenticationCallbackAPI, format_url("system/authentication/callback/")
)
api.add_resource(
OSMTeamsAuthenticationCallbackAPI, format_url("system/osm-teams-authentication/callback/")
OSMTeamsAuthenticationCallbackAPI,
format_url("system/osm-teams-authentication/callback/"),
)
api.add_resource(
SystemAuthenticationEmailAPI, format_url("system/authentication/email/")
Expand Down
4 changes: 2 additions & 2 deletions backend/api/system/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ def get(self):
state = AuthenticationService.generate_random_state()
osm_teams.state = state
login_url, state = osm_teams.authorization_url(
EnvironmentConfig.OSM_TEAMS_AUTH_URL
)
EnvironmentConfig.OSM_TEAMS_AUTH_URL
)
return {"auth_url": login_url, "state": state}, 200


Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/teamsAndOrgs/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,4 +659,16 @@ export default defineMessages({
id: 'management.stats.overview',
defaultMessage: 'Overview',
},
joinTeam: {
id: 'teamsAndOrgs.management.button.join_team',
defaultMessage: 'Join team',
},
cancelRequest: {
id: 'teamsAndOrgs.management.button.cancel_request',
defaultMessage: 'Cancel request',
},
leaveTeam: {
id: 'teamsAndOrgs.management.button.leave_team',
defaultMessage: 'Leave team',
},
});
81 changes: 79 additions & 2 deletions frontend/src/components/teamsAndOrgs/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import { Form, Field, useFormState } from 'react-final-form';
import ReactTooltip from 'react-tooltip';

import messages from './messages';
import { InfoIcon } from '../svgIcons';
import { ExternalLinkIcon, InfoIcon } from '../svgIcons';
import { useEditTeamAllowed } from '../../hooks/UsePermissions';
import { UserAvatar, UserAvatarList } from '../user/avatar';
import { AddButton, ViewAllLink, Management, VisibilityBox, JoinMethodBox } from './management';
import { RadioField, OrganisationSelectInput, TextField } from '../formInputs';
import { Button, EditButton } from '../button';
import { Button, CustomButton, EditButton } from '../button';
import { nCardPlaceholders } from './teamsPlaceholder';
import { OSM_TEAMS_API_URL } from '../../config';
import { Alert } from '../alert';
import Popup from 'reactjs-popup';
import { LeaveTeamConfirmationAlert } from './leaveTeamConfirmationAlert';

export function TeamsManagement({
teams,
Expand Down Expand Up @@ -416,6 +420,23 @@ export function TeamSideBar({ team, members, managers, requestedToJoin }: Object
</span>
)}
</div>
{team.osm_teams_id && (
<Alert type="info">
<FormattedMessage
{...messages.osmTeamsReSyncHelp}
values={{ osmTeams: 'OSM Teams' }}
/>{' '}
<a
target="_blank"
rel="noopener noreferrer"
href={`${OSM_TEAMS_API_URL}/teams/${team.osm_teams_id}`}
className="blue-grey link o-75 bn f5"
>
<FormattedMessage {...messages.openOnOsmTeams} />
<ExternalLinkIcon className={'pl1'} />
</a>
</Alert>
)}
</div>
</div>
</ReactPlaceholder>
Expand Down Expand Up @@ -463,3 +484,59 @@ export const TeamBox = ({ team, className }: Object) => (
</div>
</Link>
);

export const TeamDetailPageFooter = ({ team, isMember, joinTeamFn, leaveTeamFn }) => {
return (
<div className="fixed bottom-0 cf bg-white h3 w-100">
<div
className={`${
team.joinMethod === 'BY_INVITE' && !isMember ? 'w-100-ns' : 'w-80-ns'
} w-60-m w-50 h-100 fl tr`}
>
<Link to={'/contributions/teams'}>
<CustomButton className="bg-white mr5 pr2 h-100 bn bg-white blue-dark">
<FormattedMessage {...messages.myTeams} />
</CustomButton>
</Link>
</div>
<div className="w-20-l w-40-m w-50 h-100 fr">
{isMember ? (
<Popup
trigger={
<CustomButton
className="w-100 h-100 bg-red b--red white"
disabledClassName="bg-red b--red o-50 white w-100 h-100"
disabled={team.joinMethod === 'OSM_TEAMS'}
>
<FormattedMessage
{...messages[isMember === 'requested' ? 'cancelRequest' : 'leaveTeam']}
/>
</CustomButton>
}
modal
closeOnEscape
>
{(close) => (
<LeaveTeamConfirmationAlert
teamName={team.name}
close={close}
leaveTeam={leaveTeamFn}
/>
)}
</Popup>
) : (
team.joinMethod !== 'BY_INVITE' && (
<CustomButton
className="w-100 h-100 bg-red b--red white"
disabledClassName="bg-red b--red o-50 white w-100 h-100"
onClick={() => joinTeamFn()}
disabled={team.joinMethod === 'OSM_TEAMS'}
>
<FormattedMessage {...messages.joinTeam} />
</CustomButton>
)
)}
</div>
</div>
);
}
148 changes: 147 additions & 1 deletion frontend/src/components/teamsAndOrgs/tests/teams.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import TestRenderer from 'react-test-renderer';
import { render, screen, waitFor, act } from '@testing-library/react';
import { FormattedMessage } from 'react-intl';
import { MemoryRouter } from 'react-router-dom';
import userEvent from '@testing-library/user-event';

import {
createComponentWithIntl,
Expand All @@ -11,7 +12,7 @@ import {
renderWithRouter,
createComponentWithMemoryRouter,
} from '../../../utils/testWithIntl';
import { TeamBox, TeamsBoxList, TeamsManagement, Teams, TeamCard, TeamSideBar } from '../teams';
import { TeamBox, TeamsBoxList, TeamsManagement, Teams, TeamCard, TeamSideBar, TeamDetailPageFooter } from '../teams';
import { store } from '../../../store';
import { teams, team } from '../../../network/tests/mockData/teams';

Expand Down Expand Up @@ -401,4 +402,149 @@ describe('TeamSideBar component', () => {
}),
).not.toBeInTheDocument();
});

it('when OSM Teams sync is enabled, it should show a message', () => {
const teamWithOSMTeams = {...team};
teamWithOSMTeams.osm_teams_id = 1234;
teamWithOSMTeams.joinMethod = 'OSM_TEAMS';
renderWithRouter(
<ReduxIntlProviders>
<TeamSideBar team={teamWithOSMTeams} managers={[]} members={team.members} />
</ReduxIntlProviders>,
);

expect(
screen.getByText(
'The members and managers of this team are configured through the OSM Teams platform.'
)
).toBeInTheDocument();
expect(
screen.getByText('Open on OSM Teams').href.endsWith('/teams/1234')
).toBeTruthy();
});
});


describe('TeamDetailPageFooter component', () => {
const joinTeamFn = jest.fn();
const leaveTeamFn = jest.fn();

it('has Join team button enabled for ANY joinMethod if user is not member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'ANY', name: 'The #1 Team'}}
isMember={false}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Join team').disabled).toBeFalsy();
await userEvent.click(screen.getByText('Join team'));
expect(joinTeamFn).toHaveBeenCalledTimes(1);
expect(
screen.getByRole('link').href.endsWith('/contributions/teams')
).toBeTruthy();
});

it('has Leave team button enabled for ANY joinMethod if user is a member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'ANY', name: 'The #1 Team'}}
isMember={true}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Leave team').disabled).toBeFalsy();
await userEvent.click(screen.getByText('Leave team'));
await userEvent.click(screen.getByText('Leave'));
expect(leaveTeamFn).toHaveBeenCalledTimes(1);
expect(
screen.getByRole('link').href.endsWith('/contributions/teams')
).toBeTruthy();
});

it('has Join team button enabled for BY_REQUEST joinMethod if user is not member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'BY_REQUEST', name: 'The #1 Team'}}
isMember={false}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Join team').disabled).toBeFalsy();
await userEvent.click(screen.getByText('Join team'));
expect(joinTeamFn).toHaveBeenCalledTimes(1);
});

it('has Leave team button enabled for BY_REQUEST joinMethod if user is a member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'BY_REQUEST', name: 'The #1 Team'}}
isMember={true}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Leave team').disabled).toBeFalsy();
await userEvent.click(screen.getByText('Leave team'));
await userEvent.click(screen.getByText('Leave'));
expect(leaveTeamFn).toHaveBeenCalledTimes(1);
});

it('has Leave team button enabled for BY_INVITE joinMethod if user is a member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'BY_INVITE', name: 'The #1 Team'}}
isMember={true}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Leave team').disabled).toBeFalsy();
await userEvent.click(screen.getByText('Leave team'));
await userEvent.click(screen.getByText('Leave'));
expect(leaveTeamFn).toHaveBeenCalledTimes(1);
});

it('has Join team button disabled for OSM_TEAMS joinMethod if user is not a member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'OSM_TEAMS', name: 'The #1 Team'}}
isMember={false}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Join team').disabled).toBeTruthy();
await userEvent.click(screen.getByText('Join team'));
expect(joinTeamFn).toHaveBeenCalledTimes(0);
});

it('has Leave team button disabled for OSM_TEAMS joinMethod if user is a member', async () => {
renderWithRouter(
<ReduxIntlProviders>
<TeamDetailPageFooter
team={{joinMethod: 'OSM_TEAMS', name: 'The #1 Team'}}
isMember={true}
joinTeamFn={joinTeamFn}
leaveTeamFn={leaveTeamFn}
/>
</ReduxIntlProviders>
);
expect(screen.getByText('Leave team').disabled).toBeTruthy();
});
});
16 changes: 0 additions & 16 deletions frontend/src/views/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,6 @@ export default defineMessages({
id: 'teamsAndOrgs.management.campaign.button.create',
defaultMessage: 'Create campaign',
},
myTeams: {
id: 'teamsAndOrgs.management.button.my_teams',
defaultMessage: 'My teams',
},
joinTeam: {
id: 'teamsAndOrgs.management.button.join_team',
defaultMessage: 'Join team',
},
cancelRequest: {
id: 'teamsAndOrgs.management.button.cancel_request',
defaultMessage: 'Cancel request',
},
leaveTeam: {
id: 'teamsAndOrgs.management.button.leave_team',
defaultMessage: 'Leave team',
},
cancel: {
id: 'teamsAndOrgs.management.button.cancel',
defaultMessage: 'Cancel',
Expand Down
Loading

0 comments on commit acfdf31

Please sign in to comment.