summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2024-02-09 17:45:33 +0100
committerOskar Nyberg <oskar@mullvad.net>2024-02-09 17:45:33 +0100
commit3bea8ae918ba35755b166a44ac90ef1388e25a86 (patch)
tree6542fa3e5d07942c9ac536f6056e124b694e1b91
parent433547101d2c4e26c5224381a72330bce1ce51fb (diff)
parent683fbbced61ce9379b1af0be6da8be5da3a11f25 (diff)
downloadmullvadvpn-3bea8ae918ba35755b166a44ac90ef1388e25a86.tar.xz
mullvadvpn-3bea8ae918ba35755b166a44ac90ef1388e25a86.zip
Merge branch 'add-out-of-time-state-update'
-rw-r--r--gui/src/renderer/app.tsx48
-rw-r--r--gui/test/e2e/installed/state-dependent/login.spec.ts31
-rw-r--r--gui/test/e2e/mocked/expired-account-error-view.spec.ts17
3 files changed, 63 insertions, 33 deletions
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 78c11504f2..1a735e1ec5 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -3,7 +3,7 @@ import { Router } from 'react-router';
import { bindActionCreators } from 'redux';
import { StyleSheetManager } from 'styled-components';
-import { hasExpired } from '../shared/account-expiry';
+import { closeToExpiry, hasExpired } from '../shared/account-expiry';
import { ILinuxSplitTunnelingApplication, IWindowsApplication } from '../shared/application-types';
import {
AccessMethodSetting,
@@ -103,9 +103,11 @@ export default class AppRenderer {
private deviceState?: DeviceState;
private loginState: LoginState = 'none';
private previousLoginState: LoginState = 'none';
- private loginScheduler = new Scheduler();
private connectedToDaemon = false;
+ private loginScheduler = new Scheduler();
+ private expiryScheduler = new Scheduler();
+
constructor() {
log.addOutput(new ConsoleOutput(LogLevel.debug));
log.addOutput(new IpcOutput(LogLevel.debug));
@@ -669,14 +671,18 @@ export default class AppRenderer {
this.resetNavigation();
}
- private resetNavigation() {
+ private resetNavigation(replaceRoot?: boolean) {
if (this.history) {
const pathname = this.history.location.pathname as RoutePath;
const nextPath = this.getNavigationBase() as RoutePath;
if (pathname !== nextPath) {
const transition = this.getNavigationTransition(pathname, nextPath);
- this.history.reset(nextPath, { transition });
+ if (replaceRoot) {
+ this.history.replaceRoot(nextPath, { transition });
+ } else {
+ this.history.reset(nextPath, { transition });
+ }
}
}
}
@@ -927,29 +933,41 @@ export default class AppRenderer {
}
private setAccountExpiry(expiry?: string) {
- if (window.env.e2e && expiry) {
- log.verbose('Expiry of account:', expiry);
+ const state = this.reduxStore.getState();
+ const previousExpiry = state.account.expiry;
+
+ this.expiryScheduler.cancel();
+
+ if (expiry !== undefined) {
+ const expired = hasExpired(expiry);
+
+ // Set state to expired when expiry date passes.
+ if (!expired && closeToExpiry(expiry)) {
+ const delay = new Date(expiry).getTime() - Date.now() + 1;
+ this.expiryScheduler.schedule(() => this.handleExpiry(expiry, true), delay);
+ }
+
+ if (expiry !== previousExpiry) {
+ this.handleExpiry(expiry, expired);
+ }
+ } else {
+ this.handleExpiry(expiry);
}
+ }
+ private handleExpiry(expiry?: string, expired?: boolean) {
const state = this.reduxStore.getState();
- const previousExpiry = state.account.expiry;
this.reduxActions.account.updateAccountExpiry(expiry);
- const expired = expiry !== undefined && hasExpired(expiry);
if (
- this.history &&
- state.account.status.type === 'ok' &&
expiry !== undefined &&
- expiry !== previousExpiry &&
+ state.account.status.type === 'ok' &&
((state.account.status.expiredState === undefined && expired) ||
(state.account.status.expiredState === 'expired' && !expired)) &&
// If the login navigation is already scheduled no navigation is needed
!this.loginScheduler.isRunning
) {
- const prevPath = this.history.location.pathname as RoutePath;
- const nextPath = expired ? RoutePath.expired : RoutePath.timeAdded;
- const transition = this.getNavigationTransition(prevPath, nextPath);
- this.history.replaceRoot(nextPath, { transition });
+ this.resetNavigation(true);
}
}
diff --git a/gui/test/e2e/installed/state-dependent/login.spec.ts b/gui/test/e2e/installed/state-dependent/login.spec.ts
index 45444715bb..ba72a5b466 100644
--- a/gui/test/e2e/installed/state-dependent/login.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/login.spec.ts
@@ -49,12 +49,12 @@ test('App should create account', async () => {
const title = page.locator('h1')
const subtitle = page.getByTestId('subtitle');
- await page.getByText('Create account').click();
+ expect(await util.waitForNavigation(async () => {
+ await page.getByText('Create account').click();
- await expect(title).toHaveText('Account created');
- await expect(subtitle).toHaveText('Logged in');
-
- expect(await util.waitForNavigation()).toEqual(RoutePath.expired);
+ await expect(title).toHaveText('Account created');
+ await expect(subtitle).toHaveText('Logged in');
+ })).toEqual(RoutePath.expired);
const outOfTimeTitle = page.getByTestId('title');
await expect(outOfTimeTitle).toHaveText('Congrats!');
@@ -80,13 +80,14 @@ test('App should log in', async () => {
await expect(title).toHaveText('Login');
await expect(subtitle).toHaveText('Enter your account number');
- await loginInput.type(process.env.ACCOUNT_NUMBER!);
- await loginInput.press('Enter');
+ await loginInput.fill(process.env.ACCOUNT_NUMBER!);
- await expect(title).toHaveText('Logged in');
- await expect(subtitle).toHaveText('Valid account number');
+ expect(await util.waitForNavigation(async () => {
+ await loginInput.press('Enter');
- expect(await util.waitForNavigation()).toEqual(RoutePath.main);
+ await expect(title).toHaveText('Logged in');
+ await expect(subtitle).toHaveText('Valid account number');
+ })).toEqual(RoutePath.main);
await expectDisconnected(page);
});
@@ -115,13 +116,11 @@ test('App should log in to expired account', async () => {
await expect(title).toHaveText('Login');
await expect(subtitle).toHaveText('Enter your account number');
- await loginInput.type(accountNumber);
- await loginInput.press('Enter');
-
- await expect(title).toHaveText('Logged in');
- await expect(subtitle).toHaveText('Valid account number');
+ await loginInput.fill(accountNumber);
- expect(await util.waitForNavigation()).toEqual(RoutePath.expired);
+ expect(await util.waitForNavigation(async () => {
+ await loginInput.press('Enter');
+ })).toEqual(RoutePath.expired);
const outOfTimeTitle = page.getByTestId('title');
await expect(outOfTimeTitle).toHaveText('Out of time');
diff --git a/gui/test/e2e/mocked/expired-account-error-view.spec.ts b/gui/test/e2e/mocked/expired-account-error-view.spec.ts
index 0d6bacd747..5c906a134b 100644
--- a/gui/test/e2e/mocked/expired-account-error-view.spec.ts
+++ b/gui/test/e2e/mocked/expired-account-error-view.spec.ts
@@ -4,15 +4,16 @@ import { expect, test } from '@playwright/test';
import { IAccountData } from '../../../src/shared/daemon-rpc-types';
import { getBackgroundColor } from '../utils';
import { colors } from '../../../src/config.json';
+import { RoutePath } from '../../../src/renderer/lib/routes';
let page: Page;
let util: MockedTestUtils;
-test.beforeAll(async () => {
+test.beforeEach(async () => {
({ page, util } = await startMockedApp());
});
-test.afterAll(async () => {
+test.afterEach(async () => {
await page.close();
});
@@ -31,3 +32,15 @@ test('App should show Expired Account Error View', async () => {
await expect(redeemVoucherButton).toBeVisible();
expect(await getBackgroundColor(redeemVoucherButton)).toBe(colors.green);
});
+
+test('App should show out of time view after running out of time', async () => {
+ const expiryDate = new Date();
+ expiryDate.setSeconds(expiryDate.getSeconds() + 2);
+
+ expect(await util.waitForNavigation(async () => {
+ await util.sendMockIpcResponse<IAccountData>({
+ channel: 'account-',
+ response: { expiry: expiryDate.toISOString() },
+ });
+ })).toEqual(RoutePath.expired);
+});