diff options
| author | Oskar <oskar@mullvad.net> | 2025-09-16 08:09:36 +0200 |
|---|---|---|
| committer | Oskar <oskar@mullvad.net> | 2025-10-01 13:19:43 +0200 |
| commit | 5e208df04089b553dc09fb64544e7e3da3ee6b17 (patch) | |
| tree | 7533d7a840875878a8fd9ffde2e6b8223fd95874 /desktop | |
| parent | 73f89bce2f4120bbbd52218722f78583a6cb3925 (diff) | |
| download | mullvadvpn-5e208df04089b553dc09fb64544e7e3da3ee6b17.tar.xz mullvadvpn-5e208df04089b553dc09fb64544e7e3da3ee6b17.zip | |
Handle too many devices view in getNavigationBase
Diffstat (limited to 'desktop')
8 files changed, 119 insertions, 7 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/app.tsx b/desktop/packages/mullvad-vpn/src/renderer/app.tsx index c6384f5e35..85e6d80687 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/app.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/app.tsx @@ -54,7 +54,7 @@ import { ModalContainer } from './components/Modal'; import { AppContext } from './context'; import { Theme } from './lib/components'; import { getNavigationBase } from './lib/functions/navigation-base'; -import History, { TransitionType } from './lib/history'; +import History from './lib/history'; import { loadTranslations } from './lib/load-translations'; import IpcOutput from './lib/logging'; import accountActions from './redux/account/actions'; @@ -499,8 +499,6 @@ export default class AppRenderer { actions.account.loginTooManyDevices(); this.loginState = 'too many devices'; - - this.history.reset(RoutePath.tooManyDevices, { transition: TransitionType.push }); } catch { log.error('Failed to fetch device list'); actions.account.loginFailed('list-devices'); @@ -517,9 +515,8 @@ export default class AppRenderer { this.loginState = 'none'; }; - public logout = async (transition = TransitionType.dismiss) => { + public logout = async () => { try { - this.history.reset(RoutePath.login, { transition }); await IpcRendererEventChannel.account.logout(); } catch (e) { const error = e as Error; @@ -528,7 +525,7 @@ export default class AppRenderer { }; public leaveRevokedDevice = async () => { - await this.logout(TransitionType.pop); + await this.logout(); await this.disconnectTunnel(); }; diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/StateTriggeredNavigation.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/StateTriggeredNavigation.tsx index 0187bda865..3a275e2f61 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/StateTriggeredNavigation.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/StateTriggeredNavigation.tsx @@ -74,6 +74,7 @@ function getNavigationTransition(currentPath: RoutePath, nextPath: RoutePath) { [RoutePath.launch]: TransitionType.push, [RoutePath.main]: TransitionType.pop, [RoutePath.deviceRevoked]: TransitionType.pop, + [RoutePath.tooManyDevices]: TransitionType.pop, '*': TransitionType.dismiss, }, [RoutePath.main]: { @@ -96,6 +97,9 @@ function getNavigationTransition(currentPath: RoutePath, nextPath: RoutePath) { [RoutePath.deviceRevoked]: { '*': TransitionType.pop, }, + [RoutePath.tooManyDevices]: { + [RoutePath.login]: TransitionType.push, + }, }; return navigationTransitions[nextPath]?.[currentPath] ?? navigationTransitions[nextPath]?.['*']; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/functions/navigation-base.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/functions/navigation-base.ts index 96f92d483e..7802ace1e9 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/functions/navigation-base.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/functions/navigation-base.ts @@ -5,7 +5,16 @@ export function getNavigationBase(connectedToDaemon: boolean, loginState: LoginS if (connectedToDaemon) { if (loginState.type === 'none' && loginState.deviceRevoked) { return RoutePath.deviceRevoked; - } else if (loginState.type === 'none' || loginState.type === 'logging in') { + } else if ( + loginState.type === 'too many devices' || + (loginState.type === 'failed' && loginState.error === 'too-many-devices') + ) { + return RoutePath.tooManyDevices; + } else if ( + loginState.type === 'none' || + loginState.type === 'logging in' || + loginState.type === 'failed' + ) { return RoutePath.login; } else if (loginState.type === 'ok' && loginState.expiredState === 'expired') { return RoutePath.expired; diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/too-many-devices.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/too-many-devices.spec.ts new file mode 100644 index 0000000000..76e3fddee3 --- /dev/null +++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/too-many-devices.spec.ts @@ -0,0 +1,68 @@ +import { test } from '@playwright/test'; +import { Page } from 'playwright'; + +import { RoutesObjectModel } from '../route-object-models'; +import { MockedTestUtils, startMockedApp } from './mocked-utils'; + +let page: Page; +let util: MockedTestUtils; +let routes: RoutesObjectModel; + +test.describe('Too many devices', () => { + test.beforeAll(async () => { + ({ page, util } = await startMockedApp()); + routes = new RoutesObjectModel(page, util); + await routes.main.waitForRoute(); + + await util.ipc.account.device.notify({ + type: 'logged out', + deviceState: { type: 'logged out' }, + }); + + await routes.login.waitForRoute(); + }); + + test.afterAll(async () => { + await page.close(); + }); + + test.describe('Navigation', () => { + test('App should navigate to too many devices view', async () => { + await util.ipc.account.login.handle({ type: 'error', error: 'too-many-devices' }); + await util.ipc.account.listDevices.handle([ + { + id: '1', + name: 'Device 1', + created: new Date(), + }, + { + id: '2', + name: 'Device 2', + created: new Date(), + }, + ]); + + await routes.login.fillAccountNumber('1234123412341234'); + await routes.login.loginByPressingEnter(); + + await routes.tooManyDevices.waitForRoute(); + }); + + test('App should navigate to main via login', async () => { + await util.ipc.account.login.handle(undefined); + + await routes.tooManyDevices.waitForRoute(); + + await routes.tooManyDevices.continue(); + await routes.login.waitForRoute(); + + await util.ipc.account.device.notify({ + type: 'logged in', + deviceState: { type: 'logged in', accountAndDevice: { accountNumber: '1234123412341234' } }, + }); + await util.ipc.account[''].notify({ expiry: new Date(Date.now() + 60 * 1000).toISOString() }); + + await routes.main.waitForRoute(); + }); + }); +}); diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/routes-object-model.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/routes-object-model.ts index 34951bad8a..77ec121306 100644 --- a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/routes-object-model.ts +++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/routes-object-model.ts @@ -17,6 +17,7 @@ import { SetupFinishedRouteObjectModel } from './setup-finished'; import { ShadowsocksSettingsRouteObjectModel } from './shadowsocks-settings'; import { SplitTunnelingSettingsRouteObjectModel } from './split-tunneling-settings'; import { TimeAddedRouteObjectModel } from './time-added'; +import { TooManyDevicesRouteObjectModel } from './too-many-devices'; import { UdpOverTcpSettingsRouteObjectModel } from './udp-over-tcp-settings'; import { UserInterfaceSettingsRouteObjectModel } from './user-interface-settings'; import { VoucherSuccessRouteObjectModel } from './voucher-success'; @@ -33,6 +34,7 @@ export class RoutesObjectModel { readonly timeAdded: TimeAddedRouteObjectModel; readonly setupFinished: SetupFinishedRouteObjectModel; readonly deviceRevoked: DeviceRevokedRouteObjectModel; + readonly tooManyDevices: TooManyDevicesRouteObjectModel; readonly settings: SettingsRouteObjectModel; readonly userInterfaceSettings: UserInterfaceSettingsRouteObjectModel; readonly selectLanguage: SelectLanguageRouteObjectModel; @@ -57,6 +59,7 @@ export class RoutesObjectModel { this.timeAdded = new TimeAddedRouteObjectModel(page, utils); this.setupFinished = new SetupFinishedRouteObjectModel(page, utils); this.deviceRevoked = new DeviceRevokedRouteObjectModel(utils); + this.tooManyDevices = new TooManyDevicesRouteObjectModel(page, utils); this.settings = new SettingsRouteObjectModel(page, utils); this.userInterfaceSettings = new UserInterfaceSettingsRouteObjectModel(page, utils); this.filter = new FilterRouteObjectModel(page, utils); diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/index.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/index.ts new file mode 100644 index 0000000000..47ab585d08 --- /dev/null +++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/index.ts @@ -0,0 +1,2 @@ +export * from './too-many-devices-route-object-model'; +export * from './selectors'; diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/selectors.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/selectors.ts new file mode 100644 index 0000000000..12474f3ad9 --- /dev/null +++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/selectors.ts @@ -0,0 +1,5 @@ +import { Page } from 'playwright'; + +export const createSelectors = (page: Page) => ({ + continueButton: () => page.getByRole('button', { name: 'Continue' }), +}); diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/too-many-devices-route-object-model.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/too-many-devices-route-object-model.ts new file mode 100644 index 0000000000..fe0f8c10ed --- /dev/null +++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/too-many-devices/too-many-devices-route-object-model.ts @@ -0,0 +1,24 @@ +import { Page } from 'playwright'; + +import { RoutePath } from '../../../../src/shared/routes'; +import { TestUtils } from '../../utils'; +import { createSelectors } from './selectors'; + +export class TooManyDevicesRouteObjectModel { + readonly selectors: ReturnType<typeof createSelectors>; + + constructor( + private readonly page: Page, + private readonly utils: TestUtils, + ) { + this.selectors = createSelectors(this.page); + } + + async waitForRoute() { + await this.utils.expectRoute(RoutePath.tooManyDevices); + } + + async continue() { + await this.selectors.continueButton().click(); + } +} |
