Skip to content
48 changes: 47 additions & 1 deletion apps/webapp/src/script/components/panel/userActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import {act, render} from '@testing-library/react';
import {act, fireEvent, render} from '@testing-library/react';
import {ConnectionStatus} from '@wireapp/api-client/lib/connection/';
import {CONVERSATION_TYPE} from '@wireapp/api-client/lib/conversation/';
import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team';
Expand All @@ -44,10 +44,13 @@ import {ActionsViewModel} from 'src/script/view_model/ActionsViewModel';
import {noop} from 'Util/util';

import {ActionIdentifier, Actions, UserActions} from './userActions';
import {SidebarTabs, useSidebarStore} from '../../page/leftSidebar/panels/conversations/useSidebarStore';

const actionsViewModel = {
open1to1Conversation: jest.fn(),
getOrCreate1to1Conversation: jest.fn(),
sendConnectionRequest: jest.fn(),
saveConversation: jest.fn(),
} as unknown as ActionsViewModel;

const getAllActions = (queryFunction: (id: string) => HTMLElement | null) =>
Expand Down Expand Up @@ -433,6 +436,49 @@ describe('UserActions', () => {
expect(queryByTestId('do-close')).toBeNull();
});

it('keeps the current conversation route when sending a connection request from a conversation', async () => {
const user = new User('', '', translateForTest);
const connection = new ConnectionEntity();
user.connection(connection);
user.connection()?.status(ConnectionStatus.UNKNOWN);
jest.spyOn(user, 'isAvailable').mockImplementation(ko.pureComputed(() => true));

const conversation = new Conversation('', '', CONVERSATION_PROTOCOL.PROTEUS, translateForTest);
const selfUser = new User('', '', translateForTest);
const originalSetCurrentTab = useSidebarStore.getState().setCurrentTab;
const setCurrentTab = jest.fn();
useSidebarStore.setState({setCurrentTab});
jest.spyOn(actionsViewModel, 'sendConnectionRequest').mockResolvedValue({
connectionStatus: ConnectionStatus.SENT,
conversationId: {id: 'conversation-id', domain: ''},
});
jest.spyOn(actionsViewModel, 'saveConversation').mockResolvedValue(conversation);

const {getByTestId} = renderWithRootProvider(
<UserActions
actionsViewModel={actionsViewModel}
conversation={conversation}
conversationRoleRepository={{} as ConversationRoleRepository}
isSelfActivated
onAction={noop}
selfUser={selfUser}
user={user}
/>,
);

await act(async () => {
fireEvent.click(getByTestId(ActionIdentifier[Actions.SEND_REQUEST]));
});

expect(actionsViewModel.open1to1Conversation).not.toHaveBeenCalled();
expect(setCurrentTab).not.toHaveBeenCalledWith(SidebarTabs.RECENT);
await act(async () => {
useSidebarStore.setState({setCurrentTab: originalSetCurrentTab});
});
jest.mocked(actionsViewModel.sendConnectionRequest).mockReset();
jest.mocked(actionsViewModel.saveConversation).mockReset();
});

it('displays a list when multiple actions are available in user modal', () => {
const user = new User('', '', translateForTest);
const conversation = new Conversation('', '', CONVERSATION_PROTOCOL.PROTEUS, translateForTest);
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/src/script/components/panel/userActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ const UserActions = ({
if (!conversation) {
// Only open the new conversation if we aren't currently in a conversation context
await actionsViewModel.open1to1Conversation(savedConversation);
setCurrentSidebarTab(SidebarTabs.RECENT);
}
setCurrentSidebarTab(SidebarTabs.RECENT);
onAction(Actions.SEND_REQUEST);
},
Icon: Icon.PlusIcon,
Expand Down
4 changes: 3 additions & 1 deletion apps/webapp/src/script/page/appMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import {ContentState, useAppState} from './useAppState';
import {App} from '../main/app';
import {initialiseMLSMigrationFlow} from '../mls/MLSMigration';
import {generateConversationUrl} from '../router/routeGenerator';
import {configureRoutes, navigate} from '../router/Router';
import {configureRouterWallClock, configureRoutes, navigate} from '../router/Router';
import {MainViewModel} from '../view_model/MainViewModel';
import {WarningsContainer} from '../view_model/WarningsContainer/WarningsContainer';

Expand Down Expand Up @@ -100,6 +100,7 @@ export const AppMain = (properties: AppMainProps) => {
selfUser,
conversationState = container.resolve(ConversationState),
callState = container.resolve(CallState),
wallClock,
locked,
} = properties;
const translate = mainView.translate;
Expand Down Expand Up @@ -242,6 +243,7 @@ export const AppMain = (properties: AppMainProps) => {
showUserModal({domain, id: userId}, () => navigate('/'));
};

configureRouterWallClock(wallClock);
configureRoutes({
'/': showMostRecentConversation,
'/conversation/:conversationId/:domain': showConversationMessages,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,49 @@ import React from 'react';
import {act, render} from '@testing-library/react';
import {observable} from 'knockout';

import {amplify} from 'amplify';
import {WebAppEvents} from '@wireapp/webapp-events';

import {ConversationRepository} from 'Repositories/conversation/ConversationRepository';
import type {Conversation} from 'Repositories/entity/Conversation';
import {User} from 'Repositories/entity/User';
import {SearchRepository} from 'Repositories/search/searchRepository';
import {UserRepository} from 'Repositories/user/userRepository';
import {withTheme} from 'src/script/auth/util/test/testUtil';
import {ListState} from 'src/script/page/useAppState';
import {ContentState, ListState, useAppState} from 'src/script/page/useAppState';
import * as Router from 'src/script/router/Router';
import {TestFactory} from 'test/helper/TestFactory';

import {Conversations} from './';
import {Conversations, shouldClearDeepLinkForTab} from './';
import {SidebarTabs, useSidebarStore} from './useSidebarStore';
import {translateForTest} from 'Util/test/translateForTest';

jest.mock('./conversationSidebar/conversationSidebar', () => ({
ConversationSidebar: ({onClickPreferences}: {onClickPreferences: (contentState: number) => void}) => {
const {ContentState} = require('src/script/page/useAppState');

return (
<button
title="preferencesHeadline"
type="button"
onClick={() => onClickPreferences(ContentState.PREFERENCES_ACCOUNT)}
/>
);
},
}));
type ConversationsProps = React.ComponentProps<typeof Conversations>;

const defaultParams: Omit<React.ComponentProps<typeof Conversations>, 'conversationRepository' | 'searchRepository'> = {
const defaultParams: Omit<ConversationsProps, 'conversationRepository' | 'searchRepository'> = {
listViewModel: {
switchList: jest.fn(),
openPreferences: jest.fn(),
mainViewModel: {actions: {}},
contentViewModel: {
loadPreviousContent: jest.fn(),
switchContent: jest.fn(),
},
} as any,
preferenceNotificationRepository: {notifications: observable([])} as any,
propertiesRepository: {getPreference: jest.fn(), savePreference: jest.fn()} as any,
} as unknown as ConversationsProps['listViewModel'],
preferenceNotificationRepository: {
notifications: observable([]),
} as unknown as ConversationsProps['preferenceNotificationRepository'],
propertiesRepository: {
getPreference: jest.fn(),
savePreference: jest.fn(),
} as unknown as ConversationsProps['propertiesRepository'],
selfUser: new User('', '', translateForTest),
integrationRepository: {integrations: observable([])} as any,
teamRepository: {getTeam: jest.fn()} as any,
userRepository: {users: observable([])} as any,
integrationRepository: {integrations: observable([])} as unknown as ConversationsProps['integrationRepository'],
teamRepository: {getTeam: jest.fn()} as unknown as ConversationsProps['teamRepository'],
userRepository: {
users: observable([]),
getUsersById: jest.fn().mockResolvedValue([]),
} as unknown as ConversationsProps['userRepository'],
isConversationListCollapseEnabled: false,
};

Expand Down Expand Up @@ -89,6 +93,112 @@ describe('Conversations', () => {
openPrefButton.click();
});

expect(defaultParams.listViewModel.switchList).toHaveBeenCalledWith(ListState.PREFERENCES);
expect(defaultParams.listViewModel.openPreferences).toHaveBeenCalledWith(ContentState.PREFERENCES_ACCOUNT);
});

it.each([SidebarTabs.RECENT, SidebarTabs.CELLS, SidebarTabs.CONNECT])(
'clears the deep link for unrouted tab %s',
tab => {
expect(shouldClearDeepLinkForTab(tab)).toBe(true);
},
);

it.each([SidebarTabs.PREFERENCES, SidebarTabs.MEETINGS])('keeps the deep link for routed tab %s', tab => {
expect(shouldClearDeepLinkForTab(tab)).toBe(false);
});

it('clears the deep link when switching from preferences to a conversation tab', () => {
useAppState.setState({listState: ListState.PREFERENCES});
const setHistoryParam = jest.spyOn(Router, 'setHistoryParam');
const {getByTitle} = render(
withTheme(
<Conversations
{...defaultParams}
searchRepository={searchRepository}
conversationRepository={conversationRepository}
/>,
),
);

act(() => {
getByTitle('conversationViewTooltip').click();
});

expect(setHistoryParam).toHaveBeenCalledWith('/');
});

it('preserves the conversation deep link when switching between conversation list tabs', () => {
useAppState.setState({listState: ListState.CONVERSATIONS});
useSidebarStore.setState({currentTab: SidebarTabs.RECENT});
const setHistoryParam = jest.spyOn(Router, 'setHistoryParam');
setHistoryParam.mockClear();
const {getByTitle} = render(
withTheme(
<Conversations
{...defaultParams}
searchRepository={searchRepository}
conversationRepository={conversationRepository}
/>,
),
);

act(() => {
getByTitle('conversationLabelFavorites').click();
});

expect(setHistoryParam).not.toHaveBeenCalled();
});

it('keeps Connect selected on the first click when a conversation is shown', () => {
const switchList = defaultParams.listViewModel.switchList as jest.Mock;
const {getByTitle} = render(
withTheme(
<Conversations
{...defaultParams}
searchRepository={searchRepository}
conversationRepository={conversationRepository}
/>,
),
);

act(() => {
getByTitle('searchConnect').click();
});

act(() => {
amplify.publish(WebAppEvents.CONVERSATION.SHOW, {} as Conversation);
});

expect(switchList).toHaveBeenCalledWith(ListState.CONVERSATIONS, false);
expect(useSidebarStore.getState().currentTab).toBe(SidebarTabs.CONNECT);
});

it.each([SidebarTabs.MEETINGS, SidebarTabs.PREFERENCES])(
'keeps Connect selected on the first click after visiting tab %s',
fromTab => {
useSidebarStore.getState().setCurrentTab(fromTab);

const switchList = defaultParams.listViewModel.switchList as jest.Mock;
const {getByTitle} = render(
withTheme(
<Conversations
{...defaultParams}
searchRepository={searchRepository}
conversationRepository={conversationRepository}
/>,
),
);

act(() => {
getByTitle('searchConnect').click();
});

act(() => {
amplify.publish(WebAppEvents.CONVERSATION.SHOW, {} as Conversation);
});

expect(switchList).toHaveBeenCalledWith(ListState.CONVERSATIONS, false);
expect(useSidebarStore.getState().currentTab).toBe(SidebarTabs.CONNECT);
},
);
});
Loading
Loading