Skip to content

Commit

Permalink
test: improve api of useAppSoftLock hook
Browse files Browse the repository at this point in the history
  • Loading branch information
atomrc committed Jan 17, 2024
1 parent 40a1774 commit 1de2435
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 111 deletions.
19 changes: 6 additions & 13 deletions src/script/components/AppContainer/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ export const AppContainer: FC<AppProps> = ({config, clientType}) => {

const {repository: repositories} = app;

const {isFreshMLSSelfClient, softLockLoaded = false} = useAppSoftLock(
repositories.calling,
repositories.notification,
);
const {softLockEnabled, softLockLoaded} = useAppSoftLock(repositories.calling, repositories.notification);

if (hasOtherInstance) {
app.redirectToLogin(SIGN_OUT_REASON.MULTIPLE_TABS);
Expand All @@ -90,15 +87,11 @@ export const AppContainer: FC<AppProps> = ({config, clientType}) => {
return (
<>
<AppLoader init={onProgress => app.initApp(clientType, onProgress)}>
{selfUser => (
<AppMain
app={app}
selfUser={selfUser}
mainView={mainView}
softLockLoaded={softLockLoaded}
isFreshMLSSelfClient={isFreshMLSSelfClient}
/>
)}
{selfUser => {
return softLockLoaded ? (
<AppMain app={app} selfUser={selfUser} mainView={mainView} locked={softLockEnabled} />
) : null;
}}
</AppLoader>
<StyledApp themeId={THEME_ID.DEFAULT} css={{backgroundColor: 'unset', height: '100%'}}>
<PrimaryModalComponent />
Expand Down
4 changes: 2 additions & 2 deletions src/script/components/AppLoader/AppLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import {FC, ReactElement, useEffect, useRef, useState} from 'react';
import {FC, ReactNode, useEffect, useRef, useState} from 'react';

import {LoadingBar} from 'Components/LoadingBar/LoadingBar';

Expand All @@ -27,7 +27,7 @@ import {User} from '../../entity/User';

interface AppLoaderProps {
init: (onProgress: (progress: number, message?: string) => void) => Promise<User | undefined>;
children: (selfUser: User) => ReactElement;
children: (selfUser: User) => ReactNode;
}

interface LoadingProgress {
Expand Down
101 changes: 101 additions & 0 deletions src/script/hooks/useAppSoflLock.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {renderHook} from '@testing-library/react';

import {useAppSoftLock} from './useAppSoftLock';

import {CallingRepository} from '../calling/CallingRepository';
import {isE2EIEnabled, E2EIHandler} from '../E2EIdentity';
import {NotificationRepository} from '../notification/NotificationRepository';

const isE2EIEnabledMock = isE2EIEnabled as jest.MockedFn<typeof isE2EIEnabled>;
const E2EIHandlerMock = E2EIHandler as jest.Mocked<typeof E2EIHandler>;

jest.mock('../E2EIdentity', () => ({
isE2EIEnabled: jest.fn(),
E2EIHandler: {
getInstance: jest.fn(),
},
}));

describe('useAppSoftLock', () => {
const callingRepository = {setSoftLock: jest.fn()} as unknown as CallingRepository;
const notificationRepository = {setSoftLock: jest.fn()} as unknown as NotificationRepository;

beforeEach(() => {
jest.resetAllMocks();
});

it('should not do anything if e2ei is not enabled', () => {
const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository));
expect(result.current).toEqual({softLockEnabled: false, softLockLoaded: true});
expect(callingRepository.setSoftLock).not.toHaveBeenCalledWith(true);
expect(notificationRepository.setSoftLock).not.toHaveBeenCalledWith(true);
});

it('should set soft lock to true if the user has used up the entire grace period', async () => {
isE2EIEnabledMock.mockReturnValue(true);
E2EIHandlerMock.getInstance.mockReturnValue({
on: jest.fn((eventName, callback) => callback({enrollmentConfig: {timer: {isSnoozeTimeAvailable: () => false}}})),
off: jest.fn(),
} as any);

const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository));

expect(result.current.softLockEnabled).toBe(true);
expect(result.current.softLockLoaded).toBe(true);
expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true);
expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true);
});

it('should set softLock if the device is a fresh new device', async () => {
isE2EIEnabledMock.mockReturnValue(true);
E2EIHandlerMock.getInstance.mockReturnValue({
on: jest.fn((eventName, callback) =>
callback({enrollmentConfig: {timer: {isSnoozeTimeAvailable: () => true}, isFreshMLSSelfClient: true}}),
),
off: jest.fn(),
} as any);

const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository));

expect(result.current.softLockEnabled).toBe(true);
expect(result.current.softLockLoaded).toBe(true);
expect(callingRepository.setSoftLock).toHaveBeenCalledWith(true);
expect(notificationRepository.setSoftLock).toHaveBeenCalledWith(true);
});

it('should not set softLock if the device is an old device and the grace period is not expireds', async () => {
isE2EIEnabledMock.mockReturnValue(true);
E2EIHandlerMock.getInstance.mockReturnValue({
on: jest.fn((eventName, callback) =>
callback({enrollmentConfig: {timer: {isSnoozeTimeAvailable: () => true}, isFreshMLSSelfClient: false}}),
),
off: jest.fn(),
} as any);

const {result} = renderHook(() => useAppSoftLock(callingRepository, notificationRepository));

expect(result.current.softLockEnabled).toBe(false);
expect(result.current.softLockLoaded).toBe(true);
expect(callingRepository.setSoftLock).not.toHaveBeenCalledWith(true);
expect(notificationRepository.setSoftLock).not.toHaveBeenCalledWith(true);
});
});
40 changes: 17 additions & 23 deletions src/script/hooks/useAppSoftLock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,30 @@
*
*/

import {useEffect, useState} from 'react';
import {useCallback, useEffect, useState} from 'react';

import {CallingRepository} from '../calling/CallingRepository';
import {E2EIHandler, EnrollmentConfig, isE2EIEnabled, WireIdentity} from '../E2EIdentity';
import {shouldEnableSoftLock} from '../E2EIdentity/DelayTimer/delay';
import {NotificationRepository} from '../notification/NotificationRepository';

export function useAppSoftLock(callingRepository: CallingRepository, notificationRepository: NotificationRepository) {
const [freshMLSSelfClient, setFreshMLSSelfClient] = useState(false);
const [softLockLoaded, setSoftLockLoaded] = useState(false);

const e2eiEnabled = isE2EIEnabled();

const setAppSoftLock = (isLocked: boolean) => {
setFreshMLSSelfClient(isLocked);
setSoftLockLoaded(true);
callingRepository.setSoftLock(isLocked);
notificationRepository.setSoftLock(isLocked);
};

const handleSoftLockActivation = ({
enrollmentConfig,
identity,
}: {
enrollmentConfig: EnrollmentConfig;
identity: WireIdentity;
}) => {
const isSoftLockEnabled = shouldEnableSoftLock(enrollmentConfig, identity);
setAppSoftLock(isSoftLockEnabled);
};
const [softLockEnabled, setSoftLockEnabled] = useState(false);
const [softLockLoaded, setSoftLockLoaded] = useState(!e2eiEnabled);

const handleSoftLockActivation = useCallback(
({enrollmentConfig, identity}: {enrollmentConfig: EnrollmentConfig; identity?: WireIdentity}) => {
const isSoftLockEnabled = shouldEnableSoftLock(enrollmentConfig, identity);

setSoftLockEnabled(isSoftLockEnabled);
setSoftLockLoaded(true);
callingRepository.setSoftLock(isSoftLockEnabled);
notificationRepository.setSoftLock(isSoftLockEnabled);
},
[callingRepository, notificationRepository],
);

useEffect(() => {
if (!e2eiEnabled) {
Expand All @@ -57,7 +51,7 @@ export function useAppSoftLock(callingRepository: CallingRepository, notificatio
return () => {
E2EIHandler.getInstance().off('identityUpdated', handleSoftLockActivation);
};
}, [e2eiEnabled]);
}, [e2eiEnabled, handleSoftLockActivation]);

return {isFreshMLSSelfClient: freshMLSSelfClient, softLockLoaded: e2eiEnabled ? softLockLoaded : true};
return {softLockEnabled, softLockLoaded};
}
141 changes: 68 additions & 73 deletions src/script/page/AppMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,16 @@ interface AppMainProps {
selfUser: User;
mainView: MainViewModel;
conversationState?: ConversationState;
isFreshMLSSelfClient: boolean;
softLockLoaded: boolean;
/** will block the user from being able to interact with the application (no notifications and no messages will be shown) */
locked: boolean;
}

export const AppMain: FC<AppMainProps> = ({
app,
mainView,
selfUser,
conversationState = container.resolve(ConversationState),
isFreshMLSSelfClient,
softLockLoaded = false,
locked,
}) => {
const apiContext = app.getAPIContext();

Expand Down Expand Up @@ -212,10 +211,10 @@ export const AppMain: FC<AppMainProps> = ({
}, []);

useLayoutEffect(() => {
if (!isFreshMLSSelfClient) {
if (!locked) {
initializeApp();
}
}, [isFreshMLSSelfClient]);
}, [locked]);

return (
<StyledApp
Expand All @@ -225,77 +224,73 @@ export const AppMain: FC<AppMainProps> = ({
data-uie-name="status-webapp"
data-uie-value="is-loaded"
>
{softLockLoaded && (
<>
{!isFreshMLSSelfClient && <WindowTitleUpdater />}
<RootProvider value={mainView}>
<ErrorBoundary FallbackComponent={ErrorFallback}>
{!isFreshMLSSelfClient && (
<div id="app" className="app">
{(!smBreakpoint || isLeftSidebarVisible) && (
<LeftSidebar
listViewModel={mainView.list}
selfUser={selfUser}
isActivatedAccount={isActivatedAccount}
/>
)}

{(!smBreakpoint || !isLeftSidebarVisible) && (
<MainContent
selfUser={selfUser}
isRightSidebarOpen={!!currentState}
openRightSidebar={toggleRightSidebar}
reloadApp={app.refresh}
/>
)}

{currentState && (
<RightSidebar
lastViewedMessageDetailsEntity={lastViewedMessageDetailsEntity}
currentEntity={currentEntity}
repositories={repositories}
actionsViewModel={mainView.actions}
isFederated={mainView.isFederated}
teamState={teamState}
selfUser={selfUser}
userState={userState}
/>
)}
</div>
{!locked && <WindowTitleUpdater />}
<RootProvider value={mainView}>
<ErrorBoundary FallbackComponent={ErrorFallback}>
{!locked && (
<div id="app" className="app">
{(!smBreakpoint || isLeftSidebarVisible) && (
<LeftSidebar
listViewModel={mainView.list}
selfUser={selfUser}
isActivatedAccount={isActivatedAccount}
/>
)}

<AppLock clientRepository={repositories.client} />
<WarningsContainer onRefresh={app.refresh} />

{!isFreshMLSSelfClient && (
<>
<FeatureConfigChangeNotifier selfUserId={selfUser.id} teamState={teamState} />
<FeatureConfigChangeHandler teamState={teamState} />
<CallingContainer
multitasking={mainView.multitasking}
callingRepository={repositories.calling}
mediaRepository={repositories.media}
/>

<LegalHoldModal
selfUser={selfUser}
conversationRepository={repositories.conversation}
searchRepository={repositories.search}
teamRepository={repositories.team}
clientRepository={repositories.client}
messageRepository={repositories.message}
cryptographyRepository={repositories.cryptography}
/>
</>
{(!smBreakpoint || !isLeftSidebarVisible) && (
<MainContent
selfUser={selfUser}
isRightSidebarOpen={!!currentState}
openRightSidebar={toggleRightSidebar}
reloadApp={app.refresh}
/>
)}

{/*The order of these elements matter to show proper modals stack upon each other*/}
<UserModal selfUser={selfUser} userRepository={repositories.user} />
<GroupCreationModal userState={userState} teamState={teamState} />
</ErrorBoundary>
</RootProvider>
</>
)}
{currentState && (
<RightSidebar
lastViewedMessageDetailsEntity={lastViewedMessageDetailsEntity}
currentEntity={currentEntity}
repositories={repositories}
actionsViewModel={mainView.actions}
isFederated={mainView.isFederated}
teamState={teamState}
selfUser={selfUser}
userState={userState}
/>
)}
</div>
)}

<AppLock clientRepository={repositories.client} />
<WarningsContainer onRefresh={app.refresh} />

{!locked && (
<>
<FeatureConfigChangeNotifier selfUserId={selfUser.id} teamState={teamState} />
<FeatureConfigChangeHandler teamState={teamState} />
<CallingContainer
multitasking={mainView.multitasking}
callingRepository={repositories.calling}
mediaRepository={repositories.media}
/>

<LegalHoldModal
selfUser={selfUser}
conversationRepository={repositories.conversation}
searchRepository={repositories.search}
teamRepository={repositories.team}
clientRepository={repositories.client}
messageRepository={repositories.message}
cryptographyRepository={repositories.cryptography}
/>
</>
)}

{/*The order of these elements matter to show proper modals stack upon each other*/}
<UserModal selfUser={selfUser} userRepository={repositories.user} />
<GroupCreationModal userState={userState} teamState={teamState} />
</ErrorBoundary>
</RootProvider>
</StyledApp>
);
};

0 comments on commit 1de2435

Please sign in to comment.