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

test: improve api of useAppSoftLock hook #16537

Merged
merged 3 commits into from
Jan 17, 2024
Merged
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
17 changes: 4 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} = useAppSoftLock(repositories.calling, repositories.notification);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the loaded info anymore since now enrollment and renewal happen at load time (which means the app is not loaded)


if (hasOtherInstance) {
app.redirectToLogin(SIGN_OUT_REASON.MULTIPLE_TABS);
Expand All @@ -90,15 +87,9 @@ 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 <AppMain app={app} selfUser={selfUser} mainView={mainView} locked={softLockEnabled} />;
}}
</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
98 changes: 98 additions & 0 deletions src/script/hooks/useAppSoftLock.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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});
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(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(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(callingRepository.setSoftLock).not.toHaveBeenCalledWith(true);
expect(notificationRepository.setSoftLock).not.toHaveBeenCalledWith(true);
});
});
38 changes: 15 additions & 23 deletions src/script/hooks/useAppSoftLock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,28 @@
*
*/

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 handleSoftLockActivation = useCallback(
({enrollmentConfig, identity}: {enrollmentConfig: EnrollmentConfig; identity?: WireIdentity}) => {
const isSoftLockEnabled = shouldEnableSoftLock(enrollmentConfig, identity);

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

useEffect(() => {
if (!e2eiEnabled) {
Expand All @@ -57,7 +49,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};
}
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>
);
};
Loading