diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-06-28 09:13:52 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-06-28 09:13:52 +0200 |
| commit | 77f4e40ee83b6d66544099e0ab6253780a8c358d (patch) | |
| tree | 91c28c3aab4dcd0a7f499979066c05be9871bebf /gui | |
| parent | 425a7830379f4d2c583f076d4b1d04669afc02c7 (diff) | |
| parent | f6c2af4c5787f826b479eb0e3d8ab7d539e538ff (diff) | |
| download | mullvadvpn-77f4e40ee83b6d66544099e0ab6253780a8c358d.tar.xz mullvadvpn-77f4e40ee83b6d66544099e0ab6253780a8c358d.zip | |
Merge branch 'add-device-and-account-to-header'
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/locales/messages.pot | 70 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppRouter.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountAddTime.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/HeaderBar.tsx | 116 | ||||
| -rw-r--r-- | gui/src/renderer/components/RedeemVoucher.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 27 | ||||
| -rw-r--r-- | gui/src/renderer/components/SettingsStyles.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/lib/routes.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/account-expiry.ts | 17 | ||||
| -rw-r--r-- | gui/src/shared/date-helper.ts | 86 | ||||
| -rw-r--r-- | gui/src/shared/notifications/close-to-account-expiry.ts | 10 | ||||
| -rw-r--r-- | gui/test/e2e/installed/state-dependent/login.spec.ts | 6 | ||||
| -rw-r--r-- | gui/test/e2e/mocked/settings.spec.ts | 36 | ||||
| -rw-r--r-- | gui/test/unit/date-helper.spec.ts | 97 |
14 files changed, 276 insertions, 207 deletions
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index cfa73b0d27..db4ff05229 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -35,33 +35,11 @@ msgid_plural "%d years left" msgstr[0] "" msgstr[1] "" -msgid "a day ago" -msgid_plural "%d days ago" -msgstr[0] "" -msgstr[1] "" - -msgid "a minute ago" -msgid_plural "%d minutes ago" -msgstr[0] "" -msgstr[1] "" - -msgid "a month ago" -msgid_plural "%d months ago" -msgstr[0] "" -msgstr[1] "" - -msgid "a year ago" -msgid_plural "%d years ago" -msgstr[0] "" -msgstr[1] "" - msgid "Account paid until %(expiry)s." msgstr "" -msgid "an hour ago" -msgid_plural "%d hours ago" -msgstr[0] "" -msgstr[1] "" +msgid "Account settings" +msgstr "" msgid "Any" msgstr "" @@ -167,9 +145,6 @@ msgstr "" msgid "less than a day left" msgstr "" -msgid "less than a minute ago" -msgstr "" - msgid "Next" msgstr "" @@ -535,6 +510,10 @@ msgctxt "device-management" msgid "This will delete all forwarded ports. Local settings will be saved." msgstr "" +msgctxt "device-management" +msgid "Time left: %(timeLeft)s" +msgstr "" + #. Page title informing user that the login failed due to too many registered #. devices on account. msgctxt "device-management" @@ -1069,11 +1048,6 @@ msgctxt "select-location-view" msgid "While connected, your traffic will be routed through two secure locations, the entry point and the exit point (needs to be two different VPN servers)." msgstr "" -#. Navigation button to the 'Account' view -msgctxt "settings-view" -msgid "Account" -msgstr "" - msgctxt "settings-view" msgid "App is out of sync. Please quit and restart." msgstr "" @@ -1082,10 +1056,6 @@ msgctxt "settings-view" msgid "App version" msgstr "" -msgctxt "settings-view" -msgid "OUT OF TIME" -msgstr "" - #. Navigation button to the 'Split tunneling' view msgctxt "settings-view" msgid "Split tunneling" @@ -1818,6 +1788,9 @@ msgstr "" msgid "You are running an unsupported app version." msgstr "" +msgid "less than a minute ago" +msgstr "" + msgid "Account credit expires in a day" msgid_plural "Account credit expires in %d days" msgstr[0] "" @@ -1827,3 +1800,28 @@ msgid "Account credit expires in an hour" msgid_plural "Account credit expires in %d hours" msgstr[0] "" msgstr[1] "" + +msgid "a day ago" +msgid_plural "%d days ago" +msgstr[0] "" +msgstr[1] "" + +msgid "a minute ago" +msgid_plural "%d minutes ago" +msgstr[0] "" +msgstr[1] "" + +msgid "a month ago" +msgid_plural "%d months ago" +msgstr[0] "" +msgstr[1] "" + +msgid "a year ago" +msgid_plural "%d years ago" +msgstr[0] "" +msgstr[1] "" + +msgid "an hour ago" +msgid_plural "%d hours ago" +msgstr[0] "" +msgstr[1] "" diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx index cd6e07fbbd..59cace598d 100644 --- a/gui/src/renderer/components/AppRouter.tsx +++ b/gui/src/renderer/components/AppRouter.tsx @@ -70,9 +70,9 @@ export default function AppRouter() { <Route exact path={RoutePath.voucherSuccess} component={VoucherVerificationSuccess} /> <Route exact path={RoutePath.timeAdded} component={TimeAdded} /> <Route exact path={RoutePath.setupFinished} component={SetupFinished} /> + <Route exact path={RoutePath.account} component={Account} /> <Route exact path={RoutePath.settings} component={Settings} /> <Route exact path={RoutePath.selectLanguage} component={SelectLanguage} /> - <Route exact path={RoutePath.accountSettings} component={Account} /> <Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} /> <Route exact path={RoutePath.vpnSettings} component={VpnSettings} /> <Route exact path={RoutePath.wireguardSettings} component={WireguardSettings} /> diff --git a/gui/src/renderer/components/ExpiredAccountAddTime.tsx b/gui/src/renderer/components/ExpiredAccountAddTime.tsx index 2c593217b8..3b54642978 100644 --- a/gui/src/renderer/components/ExpiredAccountAddTime.tsx +++ b/gui/src/renderer/components/ExpiredAccountAddTime.tsx @@ -151,7 +151,9 @@ export function TimeAdded(props: ITimeAddedProps) { }, [history, finish]); const duration = - props.secondsAdded !== undefined ? formatRelativeDate(props.secondsAdded * 1000, 0) : undefined; + props.secondsAdded !== undefined + ? formatRelativeDate(0, props.secondsAdded * 1000, { capitalize: true, displayMonths: true }) + : undefined; let newExpiry = ''; if (props.newExpiry !== undefined) { diff --git a/gui/src/renderer/components/HeaderBar.tsx b/gui/src/renderer/components/HeaderBar.tsx index 7c436e3c16..7ebf448e5c 100644 --- a/gui/src/renderer/components/HeaderBar.tsx +++ b/gui/src/renderer/components/HeaderBar.tsx @@ -1,13 +1,16 @@ import React, { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { sprintf } from 'sprintf-js'; import styled from 'styled-components'; import { colors } from '../../config.json'; +import { closeToExpiry, formatRemainingTime, hasExpired } from '../../shared/account-expiry'; import { TunnelState } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; +import { capitalizeEveryWord } from '../../shared/string-helpers'; import { transitions, useHistory } from '../lib/history'; import { RoutePath } from '../lib/routes'; -import { IReduxState } from '../redux/store'; +import { useSelector } from '../redux/store'; +import { tinyText } from './common-styles'; import { FocusFallback } from './Focus'; import ImageView from './ImageView'; @@ -27,39 +30,45 @@ const headerBarStyleColorMap = { interface IHeaderBarContainerProps { barStyle?: HeaderBarStyle; + accountInfoVisible: boolean; unpinnedWindow: boolean; } const HeaderBarContainer = styled.header({}, (props: IHeaderBarContainerProps) => ({ - padding: '12px 16px', + padding: '15px 16px 0px', + minHeight: props.accountInfoVisible ? '80px' : '68px', + height: props.accountInfoVisible ? '80px' : '68px', backgroundColor: headerBarStyleColorMap[props.barStyle ?? HeaderBarStyle.default], + transitionProperty: 'height, min-height', + transitionDuration: '250ms', + transitionTimingFunction: 'ease-in-out', })); const HeaderBarContent = styled.div({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - // In views without the brand components we still want the Header to have the same height. - minHeight: '51px', + height: '38px', }); interface IHeaderBarProps { barStyle?: HeaderBarStyle; className?: string; children?: React.ReactNode; + showAccountInfo?: boolean; } export default function HeaderBar(props: IHeaderBarProps) { - const unpinnedWindow = useSelector( - (state: IReduxState) => state.settings.guiSettings.unpinnedWindow, - ); + const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow); return ( <HeaderBarContainer barStyle={props.barStyle} className={props.className} + accountInfoVisible={props.showAccountInfo ?? false} unpinnedWindow={unpinnedWindow}> <HeaderBarContent>{props.children}</HeaderBarContent> + {props.showAccountInfo && <HeaderBarDeviceInfo />} </HeaderBarContainer> ); } @@ -78,12 +87,68 @@ const Title = styled(ImageView)({ export function Brand(props: React.HTMLAttributes<HTMLDivElement>) { return ( <BrandContainer {...props}> - <ImageView width={44} height={44} source="logo-icon" /> - <Title height={18} source="logo-text" /> + <ImageView width={38} height={38} source="logo-icon" /> + <Title height={15.4} source="logo-text" /> </BrandContainer> ); } +const StyledAccountInfo = styled.div({ + display: 'flex', + marginTop: '2px', + maxWidth: '100%', +}); + +const StyledDeviceLabel = styled.div(tinyText, { + fontSize: '10px', + color: colors.white80, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', +}); + +const StyledTimeLeftLabel = styled.div(tinyText, { + fontSize: '10px', + color: colors.white80, + marginLeft: '16px', + whiteSpace: 'nowrap', +}); + +function HeaderBarDeviceInfo() { + const deviceName = useSelector((state) => state.account.deviceName); + const accountExpiry = useSelector((state) => state.account.expiry); + const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false; + const formattedExpiry = isOutOfTime + ? sprintf(messages.ngettext('1 day', '%d days', 0), 0) + : accountExpiry + ? formatRemainingTime(accountExpiry) + : ''; + + return ( + <StyledAccountInfo> + <StyledDeviceLabel> + {sprintf( + // TRANSLATORS: A label that will display the newly created device name to inform the user + // TRANSLATORS: about it. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(deviceName)s - The name of the current device + messages.pgettext('device-management', 'Device name: %(deviceName)s'), + { + deviceName: capitalizeEveryWord(deviceName ?? ''), + }, + )} + </StyledDeviceLabel> + {accountExpiry && !closeToExpiry(accountExpiry) && !isOutOfTime && ( + <StyledTimeLeftLabel> + {sprintf(messages.pgettext('device-management', 'Time left: %(timeLeft)s'), { + timeLeft: formattedExpiry, + })} + </StyledTimeLeftLabel> + )} + </StyledAccountInfo> + ); +} + const HeaderBarSettingsButtonContainer = styled.button({ cursor: 'default', padding: 0, @@ -92,6 +157,10 @@ const HeaderBarSettingsButtonContainer = styled.button({ border: 'none', }); +const HeaderBarAccountButtonContainer = styled(HeaderBarSettingsButtonContainer)({ + marginRight: '16px', +}); + interface IHeaderBarSettingsButtonProps { disabled?: boolean; } @@ -120,12 +189,37 @@ export function HeaderBarSettingsButton(props: IHeaderBarSettingsButtonProps) { ); } +export function HeaderBarAccountButton() { + const history = useHistory(); + const openAccount = useCallback( + () => history.push(RoutePath.account, { transition: transitions.show }), + [history], + ); + + return ( + <HeaderBarAccountButtonContainer + onClick={openAccount} + aria-label={messages.gettext('Account settings')}> + <ImageView + height={24} + width={24} + source="icon-account" + tintColor={colors.white60} + tintHoverColor={colors.white80} + /> + </HeaderBarAccountButtonContainer> + ); +} + export function DefaultHeaderBar(props: IHeaderBarProps) { + const loggedIn = useSelector((state) => state.account.status.type === 'ok'); + return ( - <HeaderBar {...props}> + <HeaderBar showAccountInfo={loggedIn} {...props}> <FocusFallback> <Brand /> </FocusFallback> + {loggedIn && <HeaderBarAccountButton />} <HeaderBarSettingsButton /> </HeaderBar> ); diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx index 34cc89c4b0..7f637ab125 100644 --- a/gui/src/renderer/components/RedeemVoucher.tsx +++ b/gui/src/renderer/components/RedeemVoucher.tsx @@ -217,7 +217,10 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { const locale = useSelector((state) => state.userInterface.locale); if (response?.type === 'success') { - const duration = formatRelativeDate(response.secondsAdded * 1000, 0); + const duration = formatRelativeDate(0, response.secondsAdded * 1000, { + capitalize: true, + displayMonths: true, + }); const expiry = formatDate(response.newExpiry, locale); return ( diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index 1a316f81bd..ce84ae1661 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -1,7 +1,6 @@ import { useCallback, useEffect } from 'react'; import { colors, links } from '../../config.json'; -import { formatRemainingTime, hasExpired } from '../../shared/account-expiry'; import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; import { useHistory } from '../lib/history'; @@ -17,7 +16,6 @@ import { StyledCellIcon, StyledContent, StyledNavigationScrollbars, - StyledOutOfTimeSubText, StyledQuitButton, StyledSettingsContent, } from './SettingsStyles'; @@ -63,7 +61,6 @@ export default function Support() { {showSubSettings ? ( <> <Cell.Group> - <AccountButton /> <UserInterfaceSettingsButton /> <VpnSettingsButton /> </Cell.Group> @@ -102,30 +99,6 @@ export default function Support() { ); } -function AccountButton() { - const history = useHistory(); - const navigate = useCallback(() => history.push(RoutePath.accountSettings), [history]); - - const accountExpiry = useSelector((state) => state.account.expiry); - const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false; - const formattedExpiry = accountExpiry ? formatRemainingTime(accountExpiry).toUpperCase() : ''; - const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME'); - - return ( - <Cell.CellNavigationButton onClick={navigate}> - <Cell.Label> - { - // TRANSLATORS: Navigation button to the 'Account' view - messages.pgettext('settings-view', 'Account') - } - </Cell.Label> - <StyledOutOfTimeSubText isOutOfTime={isOutOfTime}> - {isOutOfTime ? outOfTimeMessage : formattedExpiry} - </StyledOutOfTimeSubText> - </Cell.CellNavigationButton> - ); -} - function UserInterfaceSettingsButton() { const history = useHistory(); const navigate = useCallback(() => history.push(RoutePath.userInterfaceSettings), [history]); diff --git a/gui/src/renderer/components/SettingsStyles.tsx b/gui/src/renderer/components/SettingsStyles.tsx index 57e7ecc7ad..7a3d2c04ae 100644 --- a/gui/src/renderer/components/SettingsStyles.tsx +++ b/gui/src/renderer/components/SettingsStyles.tsx @@ -1,15 +1,10 @@ import styled from 'styled-components'; -import { colors } from '../../config.json'; import * as AppButton from './AppButton'; import * as Cell from './cell'; import { measurements } from './common-styles'; import { NavigationScrollbars } from './NavigationBar'; -export const StyledOutOfTimeSubText = styled(Cell.SubText)((props: { isOutOfTime: boolean }) => ({ - color: props.isOutOfTime ? colors.red : undefined, -})); - export const StyledCellIcon = styled(Cell.UntintedIcon)({ marginRight: '8px', }); diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts index a0248d940e..0be9983769 100644 --- a/gui/src/renderer/lib/routes.ts +++ b/gui/src/renderer/lib/routes.ts @@ -10,7 +10,7 @@ export enum RoutePath { setupFinished = '/main/setup-finished', settings = '/settings', selectLanguage = '/settings/language', - accountSettings = '/settings/account', + account = '/account', userInterfaceSettings = '/settings/interface', vpnSettings = '/settings/vpn', wireguardSettings = '/settings/advanced/wireguard', diff --git a/gui/src/shared/account-expiry.ts b/gui/src/shared/account-expiry.ts index 85d0d78315..1b40848220 100644 --- a/gui/src/shared/account-expiry.ts +++ b/gui/src/shared/account-expiry.ts @@ -1,5 +1,10 @@ -import { dateByAddingComponent, DateComponent, DateType, formatTimeLeft } from './date-helper'; -import { capitalize } from './string-helpers'; +import { + dateByAddingComponent, + DateComponent, + DateType, + FormatDateOptions, + formatRelativeDate, +} from './date-helper'; export function hasExpired(expiry: DateType): boolean { return new Date(expiry).getTime() < Date.now(); @@ -18,10 +23,6 @@ export function formatDate(date: DateType, locale: string): string { ); } -export function formatRemainingTime( - expiry: DateType, - shouldCapitalizeFirstLetter?: boolean, -): string { - const remaining = formatTimeLeft(new Date(), expiry); - return shouldCapitalizeFirstLetter ? capitalize(remaining) : remaining; +export function formatRemainingTime(expiry: DateType, options?: FormatDateOptions): string { + return formatRelativeDate(new Date(), expiry, options); } diff --git a/gui/src/shared/date-helper.ts b/gui/src/shared/date-helper.ts index 017b2048f5..be83473cfa 100644 --- a/gui/src/shared/date-helper.ts +++ b/gui/src/shared/date-helper.ts @@ -1,6 +1,7 @@ import { sprintf } from 'sprintf-js'; import { messages } from './gettext'; +import { capitalize } from './string-helpers'; export type DateType = Date | string | number; @@ -72,74 +73,53 @@ export class DateDiff { } } +export interface FormatDateOptions { + suffix?: boolean; + displayMonths?: boolean; + capitalize?: boolean; +} + +// If withSuffix is true then "left" will be added at the end of the remaining time. +// If noMonths is true then the following applies: +// If a user has more than 2 years (730 days) left of time it should be displayed in whole years +// rounded down If a user has less than 2 years left (e.g. 729 days) then this should be displayed +// in days. export function formatRelativeDate( fromDate: DateType, toDate: DateType, - withSuffix = false, + options?: FormatDateOptions, ): string { const diff = new DateDiff(fromDate, toDate); const years = Math.abs(diff.years); const months = Math.abs(diff.months); const days = Math.abs(diff.days); - const hours = Math.abs(diff.hours); - const minutes = Math.abs(diff.minutes); - if (!withSuffix) { - if (years > 0) { - return sprintf(messages.ngettext('1 year', '%d years', years), years); - } else if (months >= 3) { - return sprintf(messages.ngettext('1 month', '%d months', months), months); + if (isNaN(years) || isNaN(months) || isNaN(days)) { + return ''; + } + + let result = ''; + if (!options?.suffix) { + if (options?.displayMonths ? years > 0 : days >= 730) { + result = sprintf(messages.ngettext('1 year', '%d years', years), years); + } else if (options?.displayMonths && months >= 3) { + result = sprintf(messages.ngettext('1 month', '%d months', months), months); } else if (days > 0) { - return sprintf(messages.ngettext('1 day', '%d days', days), days); + result = sprintf(messages.ngettext('1 day', '%d days', days), days); } else { - return messages.gettext('less than a day'); + result = messages.gettext('less than a day'); } } else if (diff.milliseconds > 0) { - if (years > 0) { - return sprintf(messages.ngettext('1 year left', '%d years left', years), years); - } else if (months >= 3) { - return sprintf(messages.ngettext('1 month left', '%d months left', months), months); + if (options?.displayMonths ? years > 0 : days >= 730) { + result = sprintf(messages.ngettext('1 year left', '%d years left', years), years); + } else if (options?.displayMonths && months >= 3) { + result = sprintf(messages.ngettext('1 month left', '%d months left', months), months); } else if (days > 0) { - return sprintf(messages.ngettext('1 day left', '%d days left', days), days); + result = sprintf(messages.ngettext('1 day left', '%d days left', days), days); } else { - return messages.gettext('less than a day left'); - } - } else { - if (years > 0) { - return sprintf(messages.ngettext('a year ago', '%d years ago', years), years); - } else if (months > 0) { - return sprintf(messages.ngettext('a month ago', '%d months ago', months), months); - } else if (days > 0) { - return sprintf(messages.ngettext('a day ago', '%d days ago', days), days); - } else if (hours > 0) { - return sprintf(messages.ngettext('an hour ago', '%d hours ago', hours), hours); - } else if (minutes > 0) { - return sprintf(messages.ngettext('a minute ago', '%d minutes ago', minutes), minutes); - } else { - return messages.gettext('less than a minute ago'); + result = messages.gettext('less than a day left'); } } -} -/** - * If a user has more than 2 years (730 days) left of time it should be displayed in whole years rounded down - * If a user has less than 2 years left (e.g. 729 days) then this should be displayed in days. - * - * @param fromDate - * @param toDate - */ -export const formatTimeLeft = (fromDate: DateType, toDate: DateType): string => { - const diff = new DateDiff(fromDate, toDate); - const years = Math.abs(diff.years); - const days = Math.abs(diff.days); - - if (days < 1) { - return messages.gettext('less than a day left'); - } - - if (days < 730) { - return sprintf(messages.ngettext('1 day left', '%d days left', days), days); - } - - return sprintf(messages.ngettext('1 year left', '%d years left', years), years); -}; + return options?.capitalize ? capitalize(result) : result; +} diff --git a/gui/src/shared/notifications/close-to-account-expiry.ts b/gui/src/shared/notifications/close-to-account-expiry.ts index a3f5e749ad..60feb7ec4d 100644 --- a/gui/src/shared/notifications/close-to-account-expiry.ts +++ b/gui/src/shared/notifications/close-to-account-expiry.ts @@ -3,7 +3,6 @@ import { sprintf } from 'sprintf-js'; import { links } from '../../config.json'; import { messages } from '../../shared/gettext'; import { closeToExpiry, formatRemainingTime } from '../account-expiry'; -import { formatRelativeDate } from '../date-helper'; import { InAppNotification, InAppNotificationProvider, @@ -34,7 +33,7 @@ export class CloseToAccountExpiryNotificationProvider 'Account credit expires in %(duration)s. Buy more credit.', ), { - duration: formatRelativeDate(new Date(), this.context.accountExpiry), + duration: formatRemainingTime(this.context.accountExpiry), }, ); @@ -54,7 +53,12 @@ export class CloseToAccountExpiryNotificationProvider public getInAppNotification(): InAppNotification { const subtitle = sprintf( messages.pgettext('in-app-notifications', '%(duration)s. Buy more credit.'), - { duration: formatRemainingTime(this.context.accountExpiry, true) }, + { + duration: formatRemainingTime(this.context.accountExpiry, { + capitalize: true, + suffix: true, + }), + }, ); return { diff --git a/gui/test/e2e/installed/state-dependent/login.spec.ts b/gui/test/e2e/installed/state-dependent/login.spec.ts index 6701cfbd1b..b860de010c 100644 --- a/gui/test/e2e/installed/state-dependent/login.spec.ts +++ b/gui/test/e2e/installed/state-dependent/login.spec.ts @@ -92,12 +92,8 @@ test('App should log in', async () => { test('App should log out', async () => { expect(await util.waitForNavigation(() => { - void page.getByLabel('Settings').click(); - })).toEqual(RoutePath.settings); - - expect(await util.waitForNavigation(() => { void page.getByText('Account').click(); - })).toEqual(RoutePath.accountSettings); + })).toEqual(RoutePath.account); expect(await util.waitForNavigation(() => { void page.getByText('Log out').click(); diff --git a/gui/test/e2e/mocked/settings.spec.ts b/gui/test/e2e/mocked/settings.spec.ts index e495c68b25..c6022678f5 100644 --- a/gui/test/e2e/mocked/settings.spec.ts +++ b/gui/test/e2e/mocked/settings.spec.ts @@ -9,27 +9,20 @@ let util: MockedTestUtils; test.beforeAll(async () => { ({ page, util } = await startMockedApp()); - await util.waitForNavigation(() => page.click('button[aria-label="Settings"]')); }); test.afterAll(async () => { await page.close(); }); -test('Settings Page', async () => { - const title = page.locator('h1'); - await expect(title).toContainText('Settings'); - - const closeButton = page.locator('button[aria-label="Close"]'); - await expect(closeButton).toBeVisible(); -}); - test('Account button should be displayed correctly', async () => { - const accountButton = page.locator('button:has-text("Account")'); + const accountButton = page.getByLabel('Account settings'); await expect(accountButton).toBeVisible(); +}); - let expiryText = accountButton.locator('span'); - await expect(expiryText).toContainText(/29 days left/i); +test('Headerbar account info should be displayed correctly', async () => { + let expiryText = page.getByText(/^Time left:/); + await expect(expiryText).toContainText(/Time left: 29 days/i); /** * 729 days left @@ -39,8 +32,7 @@ test('Account button should be displayed correctly', async () => { channel: 'account-', response: { expiry: new Date(Date.now() + 730 * 24 * 60 * 60 * 1000 - 1000).toISOString() }, }); - expiryText = accountButton.locator('span'); - await expect(expiryText).toContainText(/729 days left/i); + await expect(expiryText).toContainText(/Time left: 729 days/i); /** * 2 years left @@ -49,8 +41,7 @@ test('Account button should be displayed correctly', async () => { channel: 'account-', response: { expiry: new Date(Date.now() + 731 * 24 * 60 * 60 * 1000).toISOString() }, }); - expiryText = accountButton.locator('span'); - await expect(expiryText).toContainText(/2 years left/i); + await expect(expiryText).toContainText(/Time left: 2 years/i); /** * Expiry 1 day ago should show 'out of time' @@ -59,6 +50,15 @@ test('Account button should be displayed correctly', async () => { channel: 'account-', response: { expiry: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString() }, }); - expiryText = accountButton.locator('span'); - await expect(expiryText).toContainText(/out of time/i); + await expect(expiryText).not.toBeVisible(); +}); + +test('Settings Page', async () => { + await util.waitForNavigation(() => page.click('button[aria-label="Settings"]')); + + const title = page.locator('h1'); + await expect(title).toContainText('Settings'); + + const closeButton = page.locator('button[aria-label="Close"]'); + await expect(closeButton).toBeVisible(); }); diff --git a/gui/test/unit/date-helper.spec.ts b/gui/test/unit/date-helper.spec.ts index 5f44c53a2d..74500ff960 100644 --- a/gui/test/unit/date-helper.spec.ts +++ b/gui/test/unit/date-helper.spec.ts @@ -100,66 +100,89 @@ describe('Date helper', () => { }); it('should format positive difference as string', () => { - const diff1 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-01-01 13:37:20'); + const diff1 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-01-01 13:37:20', + { displayMonths: true }, + ); expect(diff1).to.equal('less than a day'); - const diff2 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-01-02 13:37:20'); + const diff2 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-01-02 13:37:20', + { displayMonths: true }, + ); expect(diff2).to.equal('1 day'); - const diff3 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-02-25 13:37:20'); + const diff3 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-02-25 13:37:20', + { displayMonths: true }, + ); expect(diff3).to.equal('55 days'); - const diff4 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-04-25 13:37:20'); + const diff4 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-04-25 13:37:20', + { displayMonths: true }, + ); expect(diff4).to.equal('3 months'); - const diff5 = date.formatRelativeDate('2021-01-01 13:37:10', '2031-04-25 13:37:20'); + const diff5 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2031-04-25 13:37:20', + { displayMonths: true }, + ); expect(diff5).to.equal('10 years'); }); it('should format positive difference as string suffixed with "left"', () => { - const diff1 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-01-01 13:37:20', true); + const diff1 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-01-01 13:37:20', + { suffix: true, displayMonths: true }, + ); expect(diff1).to.equal('less than a day left'); - const diff2 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-01-02 13:37:20', true); + const diff2 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-01-02 13:37:20', + { suffix: true, displayMonths: true }, + ); expect(diff2).to.equal('1 day left'); - const diff3 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-02-25 13:37:20', true); + const diff3 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-02-25 13:37:20', + { suffix: true, displayMonths: true }, + ); expect(diff3).to.equal('55 days left'); - const diff4 = date.formatRelativeDate('2021-01-01 13:37:10', '2021-04-25 13:37:20', true); + const diff4 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2021-04-25 13:37:20', + { suffix: true, displayMonths: true }, + ); expect(diff4).to.equal('3 months left'); - const diff5 = date.formatRelativeDate('2021-01-01 13:37:10', '2031-04-25 13:37:20', true); + const diff5 = date.formatRelativeDate( + '2021-01-01 13:37:10', + '2031-04-25 13:37:20', + { suffix: true, displayMonths: true }, + ); expect(diff5).to.equal('10 years left'); }); - it('should format negative difference as string', () => { - const diff1 = date.formatRelativeDate('2021-01-01 13:37:20', '2021-01-01 13:37:10', true); - expect(diff1).to.equal('less than a minute ago'); - - const diff2 = date.formatRelativeDate('2021-01-02 13:37:20', '2021-01-01 13:37:10', true); - expect(diff2).to.equal('a day ago'); - - const diff3 = date.formatRelativeDate('2021-02-25 13:37:20', '2021-01-01 13:37:10', true); - expect(diff3).to.equal('a month ago'); - - const diff4 = date.formatRelativeDate('2021-04-25 13:37:20', '2021-01-01 13:37:10', true); - expect(diff4).to.equal('3 months ago'); - - const diff5 = date.formatRelativeDate('2031-04-25 13:37:20', '2021-01-01 13:37:10', true); - expect(diff5).to.equal('10 years ago'); - }); - it('should format time left correctly', () => { - expect(date.formatTimeLeft('2022-09-01', '2022-09-01')).to.equal('less than a day left'); - expect(date.formatTimeLeft('2022-09-01', '2022-09-02')).to.equal('1 day left'); - expect(date.formatTimeLeft('2022-09-01', '2022-09-05')).to.equal('4 days left'); - expect(date.formatTimeLeft('2022-09-01', '2022-09-30')).to.equal('29 days left'); - expect(date.formatTimeLeft('2022-09-01', '2023-09-01')).to.equal('365 days left'); - expect(date.formatTimeLeft('2022-09-01', '2024-08-30')).to.equal('729 days left'); - expect(date.formatTimeLeft('2022-09-01', '2024-08-31')).to.equal('2 years left'); - expect(date.formatTimeLeft('2022-09-01', '2024-09-05')).to.equal('2 years left'); - expect(date.formatTimeLeft('2022-09-01', '2025-08-31')).to.equal('2 years left'); - expect(date.formatTimeLeft('2022-09-01', '2025-09-01')).to.equal('3 years left'); + expect(date.formatRelativeDate('2022-09-01', '2022-09-01')).to.equal('less than a day'); + expect(date.formatRelativeDate('2022-09-01', '2022-09-02')).to.equal('1 day'); + expect(date.formatRelativeDate('2022-09-01', '2022-09-05')).to.equal('4 days'); + expect(date.formatRelativeDate('2022-09-01', '2022-09-30')).to.equal('29 days'); + expect(date.formatRelativeDate('2022-09-01', '2023-09-01')).to.equal('365 days'); + expect(date.formatRelativeDate('2022-09-01', '2024-08-30')).to.equal('729 days'); + expect(date.formatRelativeDate('2022-09-01', '2024-08-31')).to.equal('2 years'); + expect(date.formatRelativeDate('2022-09-01', '2024-09-05')).to.equal('2 years'); + expect(date.formatRelativeDate('2022-09-01', '2025-08-31')).to.equal('2 years'); + expect(date.formatRelativeDate('2022-09-01', '2025-09-01')).to.equal('3 years'); }); }); |
