summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-10-15 14:06:54 +0200
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-10-15 14:06:54 +0200
commitf72fc6ec456abc355d2ef966731851e705e65872 (patch)
tree6627b68c970e3ce5965fc8720a42cde29f119cea
parent0fd34e680a7489ab8aa70803513417d8f7d89d2d (diff)
parentf42821860eb61b30ef10c0513b501f076fd69550 (diff)
downloadmullvadvpn-f72fc6ec456abc355d2ef966731851e705e65872.tar.xz
mullvadvpn-f72fc6ec456abc355d2ef966731851e705e65872.zip
Merge branch 'use-new-components-in-user-interface-settings-des-2564'
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx12
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx258
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/beta-list-item/BetaListItem.tsx67
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts2
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/SelectLanguage.tsx (renamed from desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx)60
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/UserInterfaceSettingsView.tsx93
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/AnimateMapSetting.tsx21
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/index.ts6
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/LanguageListItem.tsx30
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/MonochromaticTrayIconSetting.tsx24
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/NotificationsSetting.tsx26
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/StartMinimizedSetting.tsx24
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/UnpinnedWindowSetting.tsx24
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/forced-motion.spec.ts2
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/user-interface-settings/user-interface-settings.spec.ts132
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/route-object-models/select-language/selectors.ts5
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/route-object-models/user-interface-settings/selectors.ts5
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/utils.ts13
26 files changed, 475 insertions, 337 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx
index 561ab55877..7fc308f6c4 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/AppRouter.tsx
@@ -19,12 +19,10 @@ import ExpiredAccountErrorView from './ExpiredAccountErrorView';
import Filter from './Filter';
import Focus, { IFocusHandle } from './Focus';
import ProblemReport from './ProblemReport';
-import SelectLanguage from './SelectLanguage';
import SettingsImport from './SettingsImport';
import SettingsTextImport from './SettingsTextImport';
import StateTriggeredNavigation from './StateTriggeredNavigation';
import Support from './Support';
-import UserInterfaceSettings from './UserInterfaceSettings';
import {
Account,
AppInfoView,
@@ -37,11 +35,13 @@ import {
ManageDevicesView,
MultihopSettingsView,
OpenVpnSettingsView,
+ SelectLanguageView,
SettingsView,
ShadowsocksSettingsView,
SplitTunnelingView,
TooManyDevicesView,
UdpOverTcpSettingsView,
+ UserInterfaceSettingsView,
VpnSettingsView,
WireguardSettingsView,
} from './views';
@@ -71,8 +71,12 @@ export default function AppRouter() {
<Route exact path={RoutePath.setupFinished} component={SetupFinished} />
<Route exact path={RoutePath.account} component={Account} />
<Route exact path={RoutePath.settings} component={SettingsView} />
- <Route exact path={RoutePath.selectLanguage} component={SelectLanguage} />
- <Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} />
+ <Route exact path={RoutePath.selectLanguage} component={SelectLanguageView} />
+ <Route
+ exact
+ path={RoutePath.userInterfaceSettings}
+ component={UserInterfaceSettingsView}
+ />
<Route exact path={RoutePath.multihopSettings} component={MultihopSettingsView} />
<Route exact path={RoutePath.vpnSettings} component={VpnSettingsView} />
<Route exact path={RoutePath.wireguardSettings} component={WireguardSettingsView} />
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx
deleted file mode 100644
index 5a1e4ad39e..0000000000
--- a/desktop/packages/mullvad-vpn/src/renderer/components/UserInterfaceSettings.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-import styled from 'styled-components';
-
-import { messages } from '../../shared/gettext';
-import { RoutePath } from '../../shared/routes';
-import { useAppContext } from '../context';
-import { Image } from '../lib/components';
-import { useHistory } from '../lib/history';
-import { useSelector } from '../redux/store';
-import { AppNavigationHeader } from './';
-import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
-import * as Cell from './cell';
-import { BackAction } from './KeyboardNavigation';
-import { Layout, SettingsContainer, SettingsContent, SettingsGroup, SettingsStack } from './Layout';
-import { NavigationContainer } from './NavigationContainer';
-import { NavigationScrollbars } from './NavigationScrollbars';
-import { SettingsNavigationListItem } from './settings-navigation-list-item';
-import SettingsHeader, { HeaderTitle } from './SettingsHeader';
-
-const StyledAnimateMapCellGroup = styled(SettingsGroup)({
- '@media (prefers-reduced-motion: reduce)': {
- display: 'none',
- },
-});
-
-export default function UserInterfaceSettings() {
- const { pop } = useHistory();
- const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow);
-
- return (
- <BackAction action={pop}>
- <Layout>
- <SettingsContainer>
- <NavigationContainer>
- <AppNavigationHeader
- title={
- // TRANSLATORS: Title label in navigation bar
- messages.pgettext('user-interface-settings-view', 'User interface settings')
- }
- />
-
- <NavigationScrollbars>
- <SettingsHeader>
- <HeaderTitle>
- {messages.pgettext('user-interface-settings-view', 'User interface settings')}
- </HeaderTitle>
- </SettingsHeader>
-
- <SettingsContent>
- <SettingsStack>
- <SettingsGroup>
- <NotificationsSetting />
- </SettingsGroup>
- <SettingsGroup>
- <MonochromaticTrayIconSetting />
- </SettingsGroup>
-
- <SettingsGroup>
- <LanguageButton />
- </SettingsGroup>
-
- {(window.env.platform === 'win32' ||
- (window.env.platform === 'darwin' && window.env.development)) && (
- <SettingsGroup>
- <UnpinnedWindowSetting />
- </SettingsGroup>
- )}
-
- {unpinnedWindow && (
- <SettingsGroup>
- <StartMinimizedSetting />
- </SettingsGroup>
- )}
-
- <StyledAnimateMapCellGroup>
- <AnimateMapSetting />
- </StyledAnimateMapCellGroup>
- </SettingsStack>
- </SettingsContent>
- </NavigationScrollbars>
- </NavigationContainer>
- </SettingsContainer>
- </Layout>
- </BackAction>
- );
-}
-
-function NotificationsSetting() {
- const enableSystemNotifications = useSelector(
- (state) => state.settings.guiSettings.enableSystemNotifications,
- );
- const { setEnableSystemNotifications } = useAppContext();
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {messages.pgettext('user-interface-settings-view', 'Notifications')}
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={enableSystemNotifications} onChange={setEnableSystemNotifications} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {messages.pgettext(
- 'user-interface-settings-view',
- 'Enable or disable system notifications. The critical notifications will always be displayed.',
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function MonochromaticTrayIconSetting() {
- const monochromaticIcon = useSelector((state) => state.settings.guiSettings.monochromaticIcon);
- const { setMonochromaticIcon } = useAppContext();
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {messages.pgettext('user-interface-settings-view', 'Monochromatic tray icon')}
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={monochromaticIcon} onChange={setMonochromaticIcon} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {messages.pgettext(
- 'user-interface-settings-view',
- 'Use a monochromatic tray icon instead of a colored one.',
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function UnpinnedWindowSetting() {
- const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow);
- const { setUnpinnedWindow } = useAppContext();
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {messages.pgettext('user-interface-settings-view', 'Unpin app from taskbar')}
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={unpinnedWindow} onChange={setUnpinnedWindow} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {messages.pgettext(
- 'user-interface-settings-view',
- 'Enable to move the app around as a free-standing window.',
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function StartMinimizedSetting() {
- const startMinimized = useSelector((state) => state.settings.guiSettings.startMinimized);
- const { setStartMinimized } = useAppContext();
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {messages.pgettext('user-interface-settings-view', 'Start minimized')}
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={startMinimized} onChange={setStartMinimized} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {messages.pgettext(
- 'user-interface-settings-view',
- 'Show only the tray icon when the app starts.',
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function AnimateMapSetting() {
- const animateMap = useSelector((state) => state.settings.guiSettings.animateMap);
- const { setAnimateMap } = useAppContext();
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>
- {messages.pgettext('user-interface-settings-view', 'Animate map')}
- </Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.Switch isOn={animateMap} onChange={setAnimateMap} />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {messages.pgettext('user-interface-settings-view', 'Animate map movements.')}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function LanguageButton() {
- const { getPreferredLocaleDisplayName } = useAppContext();
- const preferredLocale = useSelector((state) => state.settings.guiSettings.preferredLocale);
- const localeDisplayName = getPreferredLocaleDisplayName(preferredLocale);
-
- return (
- <SettingsNavigationListItem to={RoutePath.selectLanguage}>
- <SettingsNavigationListItem.Group>
- <Image source="icon-language" />
- <SettingsNavigationListItem.Label>
- {
- // TRANSLATORS: Navigation button to the 'Language' settings view
- messages.pgettext('user-interface-settings-view', 'Language')
- }
- </SettingsNavigationListItem.Label>
- </SettingsNavigationListItem.Group>
- <SettingsNavigationListItem.Group>
- <SettingsNavigationListItem.Text>{localeDisplayName}</SettingsNavigationListItem.Text>
- <SettingsNavigationListItem.Icon icon="chevron-right" />
- </SettingsNavigationListItem.Group>
- </SettingsNavigationListItem>
- );
-}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/beta-list-item/BetaListItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/beta-list-item/BetaListItem.tsx
index 9dbbb0d566..d17c947b67 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/beta-list-item/BetaListItem.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/app-info/components/beta-list-item/BetaListItem.tsx
@@ -1,51 +1,36 @@
-import React from 'react';
-
import { messages } from '../../../../../../shared/gettext';
-import { ListItem } from '../../../../../lib/components/list-item';
import { useSettingsShowBetaReleases, useVersionIsBeta } from '../../../../../redux/hooks';
-import Switch from '../../../../Switch';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
export function BetaListItem() {
const { isBeta } = useVersionIsBeta();
const { showBetaReleases, setShowBetaReleases } = useSettingsShowBetaReleases();
- const switchId = React.useId();
- const labelId = React.useId();
- const descriptionId = React.useId();
return (
- <ListItem disabled={isBeta}>
- <ListItem.Item>
- <ListItem.Content>
- <ListItem.Label id={labelId} as="label" htmlFor={switchId}>
- {
- // TRANSLATORS: Label for switch to toggle beta program.
- messages.pgettext('app-info-view', 'Beta program')
- }
- </ListItem.Label>
- <Switch
- id={switchId}
- aria-labelledby={labelId}
- aria-describedby={descriptionId}
- isOn={showBetaReleases}
- onChange={setShowBetaReleases}
- />
- </ListItem.Content>
- </ListItem.Item>
- <ListItem.Footer>
- <ListItem.Text id={descriptionId}>
- {isBeta
- ? // TRANSLATORS: Description for beta program switch when using a beta version.
- messages.pgettext(
- 'app-info-view',
- 'This option is unavailable while using a beta version.',
- )
- : // TRANSLATORS: Description for beta program switch.
- messages.pgettext(
- 'app-info-view',
- 'Enable to get notified when new beta versions of the app are released.',
- )}
- </ListItem.Text>
- </ListItem.Footer>
- </ListItem>
+ <SettingsToggleListItem
+ checked={showBetaReleases}
+ onCheckedChange={setShowBetaReleases}
+ disabled={isBeta}
+ description={
+ isBeta
+ ? // TRANSLATORS: Description for beta program switch when using a beta version.
+ messages.pgettext(
+ 'app-info-view',
+ 'This option is unavailable while using a beta version.',
+ )
+ : // TRANSLATORS: Description for beta program switch.
+ messages.pgettext(
+ 'app-info-view',
+ 'Enable to get notified when new beta versions of the app are released.',
+ )
+ }>
+ <SettingsToggleListItem.Label>
+ {
+ // TRANSLATORS: Label for switch to toggle beta program.
+ messages.pgettext('app-info-view', 'Beta program')
+ }
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
);
}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts
index dbd592ca0f..d6141c4dda 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/index.ts
@@ -10,9 +10,11 @@ export * from './login';
export * from './open-vpn-settings';
export * from './changelog';
export * from './settings';
+export * from './select-language';
export * from './shadowsocks-settings';
export * from './split-tunneling';
export * from './too-many-devices';
export * from './udp-over-tcp-settings';
export * from './vpn-settings';
+export * from './user-interface-settings';
export * from './wireguard-settings';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/SelectLanguage.tsx
index 91ee78429a..79eda22302 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/SelectLanguage.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/SelectLanguage.tsx
@@ -1,25 +1,20 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
-import styled from 'styled-components';
-import { useAppContext } from '../../renderer/context';
-import { messages } from '../../shared/gettext';
-import { useHistory } from '../lib/history';
-import { useSelector } from '../redux/store';
-import { AppNavigationHeader } from './';
-import { AriaInputGroup } from './AriaGroup';
-import Selector, { SelectorItem } from './cell/Selector';
-import { CustomScrollbarsRef } from './CustomScrollbars';
-import { BackAction } from './KeyboardNavigation';
-import { Layout, SettingsContainer } from './Layout';
-import { NavigationContainer } from './NavigationContainer';
-import { NavigationScrollbars } from './NavigationScrollbars';
-import SettingsHeader, { HeaderTitle } from './SettingsHeader';
+import { messages } from '../../../../shared/gettext';
+import { useAppContext } from '../../../context';
+import { Listbox } from '../../../lib/components/listbox';
+import { useHistory } from '../../../lib/history';
+import { useSelector } from '../../../redux/store';
+import { AppNavigationHeader } from '../..';
+import { SelectorItem } from '../../cell/Selector';
+import { CustomScrollbarsRef } from '../../CustomScrollbars';
+import { BackAction } from '../../KeyboardNavigation';
+import { Layout, SettingsContainer } from '../../Layout';
+import { NavigationContainer } from '../../NavigationContainer';
+import { NavigationScrollbars } from '../../NavigationScrollbars';
+import SettingsHeader, { HeaderTitle } from '../../SettingsHeader';
-const StyledSelector = styled(Selector)({
- marginBottom: 0,
-}) as typeof Selector;
-
-export default function SelectLanguage() {
+export function SelectLanguageView() {
const { pop } = useHistory();
const { preferredLocale, preferredLocalesList, setPreferredLocale } = usePreferredLocale();
const scrollView = useRef<CustomScrollbarsRef>(null);
@@ -65,15 +60,24 @@ export default function SelectLanguage() {
{messages.pgettext('select-language-nav', 'Select language')}
</HeaderTitle>
</SettingsHeader>
- <AriaInputGroup>
- <StyledSelector
- title=""
- value={preferredLocale}
- items={preferredLocalesList}
- onSelect={selectLocale}
- selectedCellRef={selectedCellRef}
- />
- </AriaInputGroup>
+ <Listbox value={preferredLocale} onValueChange={selectLocale}>
+ <Listbox.Options>
+ {preferredLocalesList.map((locale) => (
+ <Listbox.Option key={locale.value} level={1} value={locale.value}>
+ <Listbox.Option.Trigger>
+ <Listbox.Option.Item>
+ <Listbox.Option.Content>
+ <Listbox.Option.Group>
+ <Listbox.Option.Icon icon="checkmark" />
+ <Listbox.Option.Label>{locale.label}</Listbox.Option.Label>
+ </Listbox.Option.Group>
+ </Listbox.Option.Content>
+ </Listbox.Option.Item>
+ </Listbox.Option.Trigger>
+ </Listbox.Option>
+ ))}
+ </Listbox.Options>
+ </Listbox>
</NavigationScrollbars>
</NavigationContainer>
</SettingsContainer>
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/index.ts
new file mode 100644
index 0000000000..f08f8e9bcb
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/select-language/index.ts
@@ -0,0 +1 @@
+export * from './SelectLanguage';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/UserInterfaceSettingsView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/UserInterfaceSettingsView.tsx
new file mode 100644
index 0000000000..1bc6313c2f
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/UserInterfaceSettingsView.tsx
@@ -0,0 +1,93 @@
+import styled from 'styled-components';
+
+import { messages } from '../../../../shared/gettext';
+import { useHistory } from '../../../lib/history';
+import { useSelector } from '../../../redux/store';
+import { AppNavigationHeader } from '../..';
+import { BackAction } from '../../KeyboardNavigation';
+import {
+ Layout,
+ SettingsContainer,
+ SettingsContent,
+ SettingsGroup,
+ SettingsStack,
+} from '../../Layout';
+import { NavigationContainer } from '../../NavigationContainer';
+import { NavigationScrollbars } from '../../NavigationScrollbars';
+import SettingsHeader, { HeaderTitle } from '../../SettingsHeader';
+import {
+ AnimateMapSetting,
+ LanguageListItem,
+ MonochromaticTrayIconSetting,
+ NotificationsSetting,
+ StartMinimizedSetting,
+ UnpinnedWindowSetting,
+} from './components';
+
+const StyledAnimateMapCellGroup = styled(SettingsGroup)({
+ '@media (prefers-reduced-motion: reduce)': {
+ display: 'none',
+ },
+});
+
+export function UserInterfaceSettingsView() {
+ const { pop } = useHistory();
+ const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow);
+
+ return (
+ <BackAction action={pop}>
+ <Layout>
+ <SettingsContainer>
+ <NavigationContainer>
+ <AppNavigationHeader
+ title={
+ // TRANSLATORS: Title label in navigation bar
+ messages.pgettext('user-interface-settings-view', 'User interface settings')
+ }
+ />
+
+ <NavigationScrollbars>
+ <SettingsHeader>
+ <HeaderTitle>
+ {messages.pgettext('user-interface-settings-view', 'User interface settings')}
+ </HeaderTitle>
+ </SettingsHeader>
+
+ <SettingsContent>
+ <SettingsStack>
+ <SettingsGroup>
+ <NotificationsSetting />
+ </SettingsGroup>
+ <SettingsGroup>
+ <MonochromaticTrayIconSetting />
+ </SettingsGroup>
+
+ <SettingsGroup>
+ <LanguageListItem />
+ </SettingsGroup>
+
+ {(window.env.platform === 'win32' ||
+ (window.env.platform === 'darwin' && window.env.development)) && (
+ <SettingsGroup>
+ <UnpinnedWindowSetting />
+ </SettingsGroup>
+ )}
+
+ {unpinnedWindow && (
+ <SettingsGroup>
+ <StartMinimizedSetting />
+ </SettingsGroup>
+ )}
+
+ <StyledAnimateMapCellGroup>
+ <AnimateMapSetting />
+ </StyledAnimateMapCellGroup>
+ </SettingsStack>
+ </SettingsContent>
+ </NavigationScrollbars>
+ </NavigationContainer>
+ </SettingsContainer>
+ </Layout>
+ </BackAction>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/AnimateMapSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/AnimateMapSetting.tsx
new file mode 100644
index 0000000000..ed62e4ad15
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/AnimateMapSetting.tsx
@@ -0,0 +1,21 @@
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
+
+export function AnimateMapSetting() {
+ const animateMap = useSelector((state) => state.settings.guiSettings.animateMap);
+ const { setAnimateMap } = useAppContext();
+
+ return (
+ <SettingsToggleListItem
+ checked={animateMap}
+ onCheckedChange={setAnimateMap}
+ description={messages.pgettext('user-interface-settings-view', 'Animate map movements.')}>
+ <SettingsToggleListItem.Label>
+ {messages.pgettext('user-interface-settings-view', 'Animate map')}
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/index.ts
new file mode 100644
index 0000000000..0bd2c4cffc
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/animate-map-setting/index.ts
@@ -0,0 +1 @@
+export * from './AnimateMapSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/index.ts
new file mode 100644
index 0000000000..742d9be904
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/index.ts
@@ -0,0 +1,6 @@
+export * from './animate-map-setting';
+export * from './language-list-item';
+export * from './monochromatic-tray-icon-setting';
+export * from './notifications-setting';
+export * from './start-minimized-setting';
+export * from './unpinned-window-setting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/LanguageListItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/LanguageListItem.tsx
new file mode 100644
index 0000000000..e28b6733a9
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/LanguageListItem.tsx
@@ -0,0 +1,30 @@
+import { messages } from '../../../../../../shared/gettext';
+import { RoutePath } from '../../../../../../shared/routes';
+import { useAppContext } from '../../../../../context';
+import { Image } from '../../../../../lib/components';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsNavigationListItem } from '../../../../settings-navigation-list-item';
+
+export function LanguageListItem() {
+ const { getPreferredLocaleDisplayName } = useAppContext();
+ const preferredLocale = useSelector((state) => state.settings.guiSettings.preferredLocale);
+ const localeDisplayName = getPreferredLocaleDisplayName(preferredLocale);
+
+ return (
+ <SettingsNavigationListItem to={RoutePath.selectLanguage}>
+ <SettingsNavigationListItem.Group>
+ <Image source="icon-language" />
+ <SettingsNavigationListItem.Label>
+ {
+ // TRANSLATORS: Navigation button to the 'Language' settings view
+ messages.pgettext('user-interface-settings-view', 'Language')
+ }
+ </SettingsNavigationListItem.Label>
+ </SettingsNavigationListItem.Group>
+ <SettingsNavigationListItem.Group>
+ <SettingsNavigationListItem.Text>{localeDisplayName}</SettingsNavigationListItem.Text>
+ <SettingsNavigationListItem.Icon icon="chevron-right" />
+ </SettingsNavigationListItem.Group>
+ </SettingsNavigationListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/index.ts
new file mode 100644
index 0000000000..9a6128b43d
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/language-list-item/index.ts
@@ -0,0 +1 @@
+export * from './LanguageListItem';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/MonochromaticTrayIconSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/MonochromaticTrayIconSetting.tsx
new file mode 100644
index 0000000000..7187105c6c
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/MonochromaticTrayIconSetting.tsx
@@ -0,0 +1,24 @@
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
+
+export function MonochromaticTrayIconSetting() {
+ const monochromaticIcon = useSelector((state) => state.settings.guiSettings.monochromaticIcon);
+ const { setMonochromaticIcon } = useAppContext();
+
+ return (
+ <SettingsToggleListItem
+ checked={monochromaticIcon}
+ onCheckedChange={setMonochromaticIcon}
+ description={messages.pgettext(
+ 'user-interface-settings-view',
+ 'Use a monochromatic tray icon instead of a colored one.',
+ )}>
+ <SettingsToggleListItem.Label>
+ {messages.pgettext('user-interface-settings-view', 'Monochromatic tray icon')}
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/index.ts
new file mode 100644
index 0000000000..38e9f4ae91
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/monochromatic-tray-icon-setting/index.ts
@@ -0,0 +1 @@
+export * from './MonochromaticTrayIconSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/NotificationsSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/NotificationsSetting.tsx
new file mode 100644
index 0000000000..d9445939ae
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/NotificationsSetting.tsx
@@ -0,0 +1,26 @@
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
+
+export function NotificationsSetting() {
+ const enableSystemNotifications = useSelector(
+ (state) => state.settings.guiSettings.enableSystemNotifications,
+ );
+ const { setEnableSystemNotifications } = useAppContext();
+
+ return (
+ <SettingsToggleListItem
+ checked={enableSystemNotifications}
+ onCheckedChange={setEnableSystemNotifications}
+ description={messages.pgettext(
+ 'user-interface-settings-view',
+ 'Enable or disable system notifications. The critical notifications will always be displayed.',
+ )}>
+ <SettingsToggleListItem.Label>
+ {messages.pgettext('user-interface-settings-view', 'Notifications')}
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/index.ts
new file mode 100644
index 0000000000..2f05ed81ca
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/notifications-setting/index.ts
@@ -0,0 +1 @@
+export * from './NotificationsSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/StartMinimizedSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/StartMinimizedSetting.tsx
new file mode 100644
index 0000000000..19418f9ff0
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/StartMinimizedSetting.tsx
@@ -0,0 +1,24 @@
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
+
+export function StartMinimizedSetting() {
+ const startMinimized = useSelector((state) => state.settings.guiSettings.startMinimized);
+ const { setStartMinimized } = useAppContext();
+
+ return (
+ <SettingsToggleListItem
+ checked={startMinimized}
+ onCheckedChange={setStartMinimized}
+ description={messages.pgettext(
+ 'user-interface-settings-view',
+ 'Show only the tray icon when the app starts.',
+ )}>
+ <SettingsToggleListItem.Label>
+ {messages.pgettext('user-interface-settings-view', 'Start minimized')}
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/index.ts
new file mode 100644
index 0000000000..39e6bdb574
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/start-minimized-setting/index.ts
@@ -0,0 +1 @@
+export * from './StartMinimizedSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/UnpinnedWindowSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/UnpinnedWindowSetting.tsx
new file mode 100644
index 0000000000..dc370582ce
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/UnpinnedWindowSetting.tsx
@@ -0,0 +1,24 @@
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { SettingsToggleListItem } from '../../../../settings-toggle-list-item';
+
+export function UnpinnedWindowSetting() {
+ const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow);
+ const { setUnpinnedWindow } = useAppContext();
+
+ return (
+ <SettingsToggleListItem
+ checked={unpinnedWindow}
+ onCheckedChange={setUnpinnedWindow}
+ description={messages.pgettext(
+ 'user-interface-settings-view',
+ 'Enable to move the app around as a free-standing window.',
+ )}>
+ <SettingsToggleListItem.Label>
+ {messages.pgettext('user-interface-settings-view', 'Unpin app from taskbar')}
+ </SettingsToggleListItem.Label>
+ <SettingsToggleListItem.Switch />
+ </SettingsToggleListItem>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/index.ts
new file mode 100644
index 0000000000..8f3f823fb1
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/components/unpinned-window-setting/index.ts
@@ -0,0 +1 @@
+export * from './UnpinnedWindowSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/index.ts
new file mode 100644
index 0000000000..80c9019ce3
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/user-interface-settings/index.ts
@@ -0,0 +1 @@
+export * from './UserInterfaceSettingsView';
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/forced-motion.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/forced-motion.spec.ts
index 103f61653b..2307483b2b 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/forced-motion.spec.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/forced-motion.spec.ts
@@ -13,8 +13,8 @@ test.describe('Transitions and animations', () => {
test.beforeAll(async () => {
({ page, util } = await startMockedApp());
- await page.emulateMedia({ reducedMotion: null });
routes = new RoutesObjectModel(page, util);
+ await util.setReducedMotion('no-preference');
await routes.main.waitForRoute();
});
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/user-interface-settings/user-interface-settings.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/user-interface-settings/user-interface-settings.spec.ts
index b7c571507b..4e1686d097 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/user-interface-settings/user-interface-settings.spec.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/user-interface-settings/user-interface-settings.spec.ts
@@ -1,6 +1,7 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
+import { IGuiSettingsState } from '../../../../src/shared/gui-settings-state';
import { RoutesObjectModel } from '../../route-object-models';
import { MockedTestUtils, startMockedApp } from '../mocked-utils';
@@ -19,6 +20,26 @@ test.describe('User interface settings', () => {
await routes.settings.gotoUserInterfaceSettings();
};
+ const setGuiSettings = async (state: Partial<IGuiSettingsState> = {}) => {
+ const baseState: IGuiSettingsState = {
+ enableSystemNotifications: false,
+ monochromaticIcon: false,
+ startMinimized: false,
+ animateMap: false,
+ autoConnect: false,
+ unpinnedWindow: false,
+ browsedForSplitTunnelingApplications: [],
+ changelogDisplayedForVersion: '',
+ preferredLocale: 'en',
+ updateDismissedForVersion: '',
+ };
+
+ await util.ipc.guiSettings[''].notify({
+ ...baseState,
+ ...state,
+ });
+ };
+
test.beforeAll(async () => {
await startup();
});
@@ -27,6 +48,117 @@ test.describe('User interface settings', () => {
await util?.closePage();
});
+ test.beforeEach(async () => {
+ await setGuiSettings();
+ });
+
+ test.describe('Notification settings', () => {
+ test('Should toggle notification setting', async () => {
+ const autoStartSwitch = routes.userInterfaceSettings.selectors.autoStartSwitch();
+ await expect(autoStartSwitch).toBeVisible();
+ await expect(autoStartSwitch).not.toBeChecked();
+
+ await Promise.all([
+ autoStartSwitch.click(),
+ util.ipc.guiSettings.setEnableSystemNotifications.expect(),
+ ]);
+
+ await setGuiSettings({ enableSystemNotifications: true });
+ await expect(autoStartSwitch).toBeChecked();
+ });
+ });
+
+ test.describe('Monochromatic tray icon settings', () => {
+ test('Should toggle monochromatic tray icon setting', async () => {
+ const monochromaticTrayIconSwitch =
+ routes.userInterfaceSettings.selectors.monochromaticTrayIconSwitch();
+ await expect(monochromaticTrayIconSwitch).toBeVisible();
+ await expect(monochromaticTrayIconSwitch).not.toBeChecked();
+
+ await Promise.all([
+ monochromaticTrayIconSwitch.click(),
+ util.ipc.guiSettings.setMonochromaticIcon.expect(),
+ ]);
+
+ await setGuiSettings({ monochromaticIcon: true });
+ await expect(monochromaticTrayIconSwitch).toBeChecked();
+ });
+ });
+
+ test.describe('Unpinned window setting', () => {
+ test.skip(() => process.platform !== 'win32');
+
+ test('Should toggle unpinned window setting', async () => {
+ const unpinnedWindowSwitch = routes.userInterfaceSettings.selectors.unpinnedWindowSwitch();
+ await expect(unpinnedWindowSwitch).toBeVisible();
+ await expect(unpinnedWindowSwitch).not.toBeChecked();
+
+ await Promise.all([
+ unpinnedWindowSwitch.click(),
+ util.ipc.guiSettings.setUnpinnedWindow.expect(),
+ ]);
+
+ await setGuiSettings({ unpinnedWindow: true });
+ await expect(unpinnedWindowSwitch).toBeChecked();
+ });
+ });
+
+ test.describe('Start minimized setting', () => {
+ test.skip(() => process.platform !== 'win32');
+
+ test('Should toggle start minimized setting', async () => {
+ await setGuiSettings({ unpinnedWindow: true });
+
+ const startMinimizedSwitch = routes.userInterfaceSettings.selectors.startMinimizedSwitch();
+ await expect(startMinimizedSwitch).toBeVisible();
+ await expect(startMinimizedSwitch).not.toBeChecked();
+
+ await Promise.all([
+ startMinimizedSwitch.click(),
+ util.ipc.guiSettings.setStartMinimized.expect(),
+ ]);
+
+ await setGuiSettings({ unpinnedWindow: true, startMinimized: true });
+ await expect(startMinimizedSwitch).toBeChecked();
+ });
+ });
+
+ test.describe('Animate map setting', () => {
+ test.describe('With reduced motion', () => {
+ test.beforeEach(async () => {
+ await util.setReducedMotion('reduce');
+ });
+
+ test('Should not display animate map setting', async () => {
+ const animateMapSwitch = routes.userInterfaceSettings.selectors.animateMapSwitch();
+ await expect(animateMapSwitch).not.toBeVisible();
+ });
+ });
+
+ test.describe('Without reduced motion', () => {
+ test.beforeEach(async () => {
+ await util.setReducedMotion('no-preference');
+ });
+
+ test('Should display animate map setting', async () => {
+ const animateMapSwitch = routes.userInterfaceSettings.selectors.animateMapSwitch();
+
+ await expect(animateMapSwitch).toBeVisible();
+ });
+
+ test('Should toggle animate map setting', async () => {
+ const animateMapSwitch = routes.userInterfaceSettings.selectors.animateMapSwitch();
+ await expect(animateMapSwitch).toBeVisible();
+ await expect(animateMapSwitch).not.toBeChecked();
+
+ await Promise.all([animateMapSwitch.click(), util.ipc.guiSettings.setAnimateMap.expect()]);
+
+ await setGuiSettings({ animateMap: true });
+ await expect(animateMapSwitch).toBeChecked();
+ });
+ });
+ });
+
test.describe('Select language', () => {
['Svenska', 'Deutsch', 'English', 'System default'].forEach((language) => {
test(`Should change language to ${language}`, async () => {
diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/select-language/selectors.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/select-language/selectors.ts
index eb9e86f36a..b4dbe6fa5e 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/select-language/selectors.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/select-language/selectors.ts
@@ -1,8 +1,5 @@
import { Page } from 'playwright';
export const createSelectors = (page: Page) => ({
- languageOption: (language: string) =>
- page.locator('button', {
- has: page.locator('div', { hasText: language }),
- }),
+ languageOption: (language: string) => page.getByRole('option', { name: language }),
});
diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/user-interface-settings/selectors.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/user-interface-settings/selectors.ts
index d718d188dd..52bfdf6d8e 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/user-interface-settings/selectors.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/user-interface-settings/selectors.ts
@@ -9,4 +9,9 @@ export const createSelectors = (page: Page) => ({
page.locator('button', {
hasText: label,
}),
+ autoStartSwitch: () => page.getByRole('switch', { name: 'Notifications' }),
+ monochromaticTrayIconSwitch: () => page.getByRole('switch', { name: 'Monochromatic tray icon' }),
+ unpinnedWindowSwitch: () => page.getByRole('switch', { name: 'Unpin app from taskbar' }),
+ startMinimizedSwitch: () => page.getByRole('switch', { name: 'Start minimized' }),
+ animateMapSwitch: () => page.getByRole('switch', { name: 'Animate map' }),
});
diff --git a/desktop/packages/mullvad-vpn/test/e2e/utils.ts b/desktop/packages/mullvad-vpn/test/e2e/utils.ts
index ff851bd2bd..89cfebe4f2 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/utils.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/utils.ts
@@ -19,16 +19,19 @@ export interface TestUtils {
getCurrentRoute: () => Promise<string | null>;
expectRoute: (route: string) => Promise<void>;
expectRouteChange: (trigger: TriggerFn) => Promise<void>;
+ setReducedMotion: (value: ReducedMotionValue) => Promise<void>;
}
type LaunchOptions = NonNullable<Parameters<typeof electron.launch>[0]>;
+type ReducedMotionValue = 'no-preference' | 'reduce';
+
export const startApp = async (options: LaunchOptions): Promise<StartAppResponse> => {
const app = await launch(options);
const page = await app.firstWindow();
if (!forceMotion) {
- await page.emulateMedia({ reducedMotion: 'reduce' });
+ await setReducedMotion(page, 'reduce');
}
await promiseTimeout(page.waitForEvent('load'));
@@ -41,6 +44,7 @@ export const startApp = async (options: LaunchOptions): Promise<StartAppResponse
getCurrentRoute: () => getCurrentRoute(page),
expectRoute: (route: string) => expectRoute(page, route),
expectRouteChange: (trigger: TriggerFn) => expectRouteChange(page, trigger),
+ setReducedMotion: (value: ReducedMotionValue) => setReducedMotion(page, value),
};
return { app, page, util };
@@ -83,6 +87,13 @@ async function expectRouteChange(page: Page, trigger: TriggerFn) {
await expect.poll(() => getCurrentRoute(page)).not.toMatchPath(initialRoute);
}
+async function setReducedMotion(page: Page, value: ReducedMotionValue) {
+ await page.emulateMedia({ reducedMotion: value });
+
+ const query = `(prefers-reduced-motion: ${value})`;
+ await page.evaluate((q) => window.matchMedia(q).matches, query);
+}
+
const getStyleProperty = (locator: Locator, property: string) => {
return locator.evaluate(
(el, { property }) => {