Update UserMenu theme toggle to use IconButton (#32591)
* Update UserMenu theme toggle to use IconButton This lets it use the correct compound colour based on theme Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
@@ -86,14 +86,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserMenu_contextMenu_themeButton {
|
.mx_UserMenu_contextMenu_themeButton {
|
||||||
min-width: 32px;
|
flex-shrink: 0;
|
||||||
max-width: 32px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
border-radius: 32px;
|
|
||||||
background-color: $theme-button-bg-color;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
/* to make alignment easier, create flexbox for the image */
|
/* to make alignment easier, create flexbox for the image */
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -102,6 +96,13 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
/* For enhanced visibility under contrast control */
|
/* For enhanced visibility under contrast control */
|
||||||
outline: 1px solid transparent;
|
outline: 1px solid transparent;
|
||||||
|
|
||||||
|
/* Compound overrides to match transitional designs */
|
||||||
|
padding: var(--cpd-space-2x);
|
||||||
|
svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_UserMenu_contextMenu_guestPrompts {
|
&.mx_UserMenu_contextMenu_guestPrompts {
|
||||||
|
|||||||
@@ -250,7 +250,6 @@ $visual-bell-bg-color: #800;
|
|||||||
$event-timestamp-color: var(--cpd-color-text-secondary);
|
$event-timestamp-color: var(--cpd-color-text-secondary);
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
||||||
$breadcrumb-placeholder-bg-color: #272c35;
|
$breadcrumb-placeholder-bg-color: #272c35;
|
||||||
$theme-button-bg-color: #e3e8f0;
|
|
||||||
$resend-button-divider-color: var(--cpd-color-gray-700);
|
$resend-button-divider-color: var(--cpd-color-gray-700);
|
||||||
$inlinecode-border-color: $quinary-content;
|
$inlinecode-border-color: $quinary-content;
|
||||||
$inlinecode-background-color: $system;
|
$inlinecode-background-color: $system;
|
||||||
|
|||||||
@@ -138,8 +138,6 @@ $room-icon-unread-color: var(--cpd-color-icon-tertiary);
|
|||||||
|
|
||||||
/* ******************** */
|
/* ******************** */
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
|
||||||
|
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #fff;
|
$roomtile-selected-bg-color: #fff;
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ $input-darker-bg-color: $quinary-content;
|
|||||||
$input-darker-fg-color: $secondary-content;
|
$input-darker-fg-color: $secondary-content;
|
||||||
$resend-button-divider-color: $input-darker-bg-color;
|
$resend-button-divider-color: $input-darker-bg-color;
|
||||||
$icon-button-color: var(--cpd-color-icon-tertiary);
|
$icon-button-color: var(--cpd-color-icon-tertiary);
|
||||||
$theme-button-bg-color: $quinary-content;
|
|
||||||
/* not using a compound color here for now as we want to have the same color in
|
/* not using a compound color here for now as we want to have the same color in
|
||||||
light and dark theme. Until we have a non-symetrical token for it, let's keep
|
light and dark theme. Until we have a non-symetrical token for it, let's keep
|
||||||
it hardcoded to the following value */
|
it hardcoded to the following value */
|
||||||
|
|||||||
@@ -321,7 +321,6 @@ $visual-bell-bg-color: #faa;
|
|||||||
$event-timestamp-color: var(--cpd-color-text-secondary);
|
$event-timestamp-color: var(--cpd-color-text-secondary);
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
||||||
$breadcrumb-placeholder-bg-color: #e8eef5;
|
$breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
$theme-button-bg-color: $quinary-content;
|
|
||||||
$resend-button-divider-color: $input-darker-bg-color;
|
$resend-button-divider-color: $input-darker-bg-color;
|
||||||
$inlinecode-border-color: $quinary-content;
|
$inlinecode-border-color: $quinary-content;
|
||||||
$inlinecode-background-color: $system;
|
$inlinecode-background-color: $system;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type JSX, createRef, type ReactNode } from "react";
|
import React, { type JSX, createRef, type ReactNode, useMemo } from "react";
|
||||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
ChatSolidIcon,
|
ChatSolidIcon,
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
NotificationsSolidIcon,
|
NotificationsSolidIcon,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
} from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
import { IconButton } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
@@ -31,8 +32,8 @@ import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
|||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import LogoutDialog, { shouldShowLogoutDialog } from "../views/dialogs/LogoutDialog";
|
import LogoutDialog, { shouldShowLogoutDialog } from "../views/dialogs/LogoutDialog";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { findHighContrastTheme, getCustomTheme, isHighContrastTheme } from "../../theme";
|
import { findHighContrastTheme, isHighContrastTheme } from "../../theme";
|
||||||
import { RovingAccessibleButton } from "../../accessibility/RovingTabIndex";
|
import { useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
||||||
import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
|
import AccessibleButton, { type ButtonEvent } from "../views/elements/AccessibleButton";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import { getHomePageUrl } from "../../utils/pages";
|
import { getHomePageUrl } from "../../utils/pages";
|
||||||
@@ -52,6 +53,8 @@ import PosthogTrackers from "../../PosthogTrackers";
|
|||||||
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
||||||
import { SDKContext } from "../../contexts/SDKContext";
|
import { SDKContext } from "../../contexts/SDKContext";
|
||||||
import { shouldShowFeedback } from "../../utils/Feedback";
|
import { shouldShowFeedback } from "../../utils/Feedback";
|
||||||
|
import ThemeWatcher, { ThemeWatcherEvent } from "../../settings/watchers/ThemeWatcher.ts";
|
||||||
|
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter.ts";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isPanelCollapsed: boolean;
|
isPanelCollapsed: boolean;
|
||||||
@@ -62,8 +65,6 @@ type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;
|
|||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
contextMenuPosition: PartialDOMRect | null;
|
contextMenuPosition: PartialDOMRect | null;
|
||||||
isDarkTheme: boolean;
|
|
||||||
isHighContrast: boolean;
|
|
||||||
selectedSpace?: Room | null;
|
selectedSpace?: Room | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +84,51 @@ const below = (rect: PartialDOMRect): MenuProps => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ThemeSwitchButton = (): JSX.Element => {
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex();
|
||||||
|
const themeWatcher = useMemo(() => new ThemeWatcher(), []);
|
||||||
|
const [isHighContrast, isDark] = useTypedEventEmitterState(
|
||||||
|
themeWatcher,
|
||||||
|
ThemeWatcherEvent.Change,
|
||||||
|
(theme: string) => [isHighContrastTheme(theme), themeWatcher.isUserOnDarkTheme()],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSwitchThemeClick = (ev: ButtonEvent): void => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
PosthogTrackers.trackInteraction("WebUserMenuThemeToggleButton", ev);
|
||||||
|
|
||||||
|
// Disable system theme matching if the user hits this button
|
||||||
|
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
|
||||||
|
|
||||||
|
let newTheme = isDark ? "light" : "dark";
|
||||||
|
if (isHighContrast) {
|
||||||
|
const hcTheme = findHighContrastTheme(newTheme);
|
||||||
|
if (hcTheme) {
|
||||||
|
newTheme = hcTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab
|
||||||
|
themeWatcher.recheck(newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
ref={ref}
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
className="mx_UserMenu_contextMenu_themeButton"
|
||||||
|
onClick={onSwitchThemeClick}
|
||||||
|
tooltip={isDark ? _t("user_menu|switch_theme_light") : _t("user_menu|switch_theme_dark")}
|
||||||
|
size="32px"
|
||||||
|
kind="secondary"
|
||||||
|
>
|
||||||
|
<ThemeIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default class UserMenu extends React.Component<IProps, IState> {
|
export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
public static contextType = SDKContext;
|
public static contextType = SDKContext;
|
||||||
declare public context: React.ContextType<typeof SDKContext>;
|
declare public context: React.ContextType<typeof SDKContext>;
|
||||||
@@ -97,8 +143,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
contextMenuPosition: null,
|
contextMenuPosition: null,
|
||||||
isDarkTheme: this.isUserOnDarkTheme(),
|
|
||||||
isHighContrast: this.isUserOnHighContrastTheme(),
|
|
||||||
selectedSpace: SpaceStore.instance.activeSpaceRoom,
|
selectedSpace: SpaceStore.instance.activeSpaceRoom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -111,7 +155,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
|
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
|
||||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
@@ -122,30 +165,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isUserOnDarkTheme(): boolean {
|
|
||||||
if (SettingsStore.getValue("use_system_theme")) {
|
|
||||||
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
||||||
} else {
|
|
||||||
const theme = SettingsStore.getValue("theme");
|
|
||||||
if (theme.startsWith("custom-")) {
|
|
||||||
return !!getCustomTheme(theme.substring("custom-".length)).is_dark;
|
|
||||||
}
|
|
||||||
return theme === "dark";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isUserOnHighContrastTheme(): boolean {
|
|
||||||
if (SettingsStore.getValue("use_system_theme")) {
|
|
||||||
return window.matchMedia("(prefers-contrast: more)").matches;
|
|
||||||
} else {
|
|
||||||
const theme = SettingsStore.getValue("theme");
|
|
||||||
if (theme.startsWith("custom-")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isHighContrastTheme(theme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onProfileUpdate = async (): Promise<void> => {
|
private onProfileUpdate = async (): Promise<void> => {
|
||||||
// the store triggered an update, so force a layout update. We don't
|
// the store triggered an update, so force a layout update. We don't
|
||||||
// have any state to store here for that to magically happen.
|
// have any state to store here for that to magically happen.
|
||||||
@@ -158,13 +177,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onThemeChanged = (): void => {
|
|
||||||
this.setState({
|
|
||||||
isDarkTheme: this.isUserOnDarkTheme(),
|
|
||||||
isHighContrast: this.isUserOnHighContrastTheme(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload): void => {
|
private onAction = (payload: ActionPayload): void => {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case Action.ToggleUserMenu:
|
case Action.ToggleUserMenu:
|
||||||
@@ -200,25 +212,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
this.setState({ contextMenuPosition: null });
|
this.setState({ contextMenuPosition: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSwitchThemeClick = (ev: ButtonEvent): void => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
PosthogTrackers.trackInteraction("WebUserMenuThemeToggleButton", ev);
|
|
||||||
|
|
||||||
// Disable system theme matching if the user hits this button
|
|
||||||
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
|
|
||||||
|
|
||||||
let newTheme = this.state.isDarkTheme ? "light" : "dark";
|
|
||||||
if (this.state.isHighContrast) {
|
|
||||||
const hcTheme = findHighContrastTheme(newTheme);
|
|
||||||
if (hcTheme) {
|
|
||||||
newTheme = hcTheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab
|
|
||||||
};
|
|
||||||
|
|
||||||
private onSettingsOpen = (ev: ButtonEvent, tabId?: string, props?: Record<string, any>): void => {
|
private onSettingsOpen = (ev: ButtonEvent, tabId?: string, props?: Record<string, any>): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@@ -398,17 +391,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RovingAccessibleButton
|
<ThemeSwitchButton />
|
||||||
className="mx_UserMenu_contextMenu_themeButton"
|
|
||||||
onClick={this.onSwitchThemeClick}
|
|
||||||
title={
|
|
||||||
this.state.isDarkTheme
|
|
||||||
? _t("user_menu|switch_theme_light")
|
|
||||||
: _t("user_menu|switch_theme_dark")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ThemeIcon width="16px" height="16px" />
|
|
||||||
</RovingAccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
{topSection}
|
{topSection}
|
||||||
{primaryOptionList}
|
{primaryOptionList}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
|||||||
import SettingsStore from "../SettingsStore";
|
import SettingsStore from "../SettingsStore";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import { findHighContrastTheme } from "../../theme";
|
import { findHighContrastTheme, getCustomTheme } from "../../theme";
|
||||||
import { type ActionPayload } from "../../dispatcher/payloads";
|
import { type ActionPayload } from "../../dispatcher/payloads";
|
||||||
import { SettingLevel } from "../SettingLevel";
|
import { SettingLevel } from "../SettingLevel";
|
||||||
|
|
||||||
@@ -125,6 +125,17 @@ export default class ThemeWatcher extends TypedEventEmitter<ThemeWatcherEvent, T
|
|||||||
return SettingsStore.getValue("theme");
|
return SettingsStore.getValue("theme");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if user is on a dark theme, if false implies user is on a light theme
|
||||||
|
*/
|
||||||
|
public isUserOnDarkTheme(): boolean {
|
||||||
|
const theme = this.currentTheme;
|
||||||
|
if (theme.startsWith("custom-")) {
|
||||||
|
return !!getCustomTheme(theme.substring("custom-".length)).is_dark;
|
||||||
|
}
|
||||||
|
return theme === "dark" || theme === "dark-hc";
|
||||||
|
}
|
||||||
|
|
||||||
private themeBasedOnSystem(): string | undefined {
|
private themeBasedOnSystem(): string | undefined {
|
||||||
let newTheme: string | undefined;
|
let newTheme: string | undefined;
|
||||||
if (this.preferDark.matches) {
|
if (this.preferDark.matches) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen, waitFor } from "jest-matrix-react";
|
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||||
import { DEVICE_CODE_SCOPE, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
import { DEVICE_CODE_SCOPE, type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
@@ -22,6 +22,7 @@ import Modal from "../../../../src/Modal";
|
|||||||
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
|
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
|
||||||
import { Action } from "../../../../src/dispatcher/actions";
|
import { Action } from "../../../../src/dispatcher/actions";
|
||||||
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
||||||
|
import SettingsStore from "../../../../src/settings/SettingsStore.ts";
|
||||||
|
|
||||||
describe("<UserMenu>", () => {
|
describe("<UserMenu>", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
@@ -151,4 +152,26 @@ describe("<UserMenu>", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should toggle theme on switcher click", async () => {
|
||||||
|
sdkContext.client = stubClient();
|
||||||
|
const spy = jest.spyOn(SettingsStore, "setValue");
|
||||||
|
|
||||||
|
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||||
|
render(<UserMenu isPanelCollapsed={true} />);
|
||||||
|
|
||||||
|
screen.getByRole("button", { name: /User menu/i }).click();
|
||||||
|
|
||||||
|
const themeSwitchButton = await screen.findByRole("button", { name: "Switch to dark mode" });
|
||||||
|
expect(themeSwitchButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
fireEvent.click(themeSwitchButton);
|
||||||
|
expect(spy).toHaveBeenCalledWith("use_system_theme", null, "device", false);
|
||||||
|
expect(spy).toHaveBeenCalledWith("theme", null, "device", "dark");
|
||||||
|
|
||||||
|
fireEvent.click(themeSwitchButton);
|
||||||
|
expect(spy).toHaveBeenCalledWith("use_system_theme", null, "device", false);
|
||||||
|
expect(spy).toHaveBeenCalledWith("theme", null, "device", "light");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -189,4 +189,20 @@ describe("ThemeWatcher", function () {
|
|||||||
const themeWatcher = new ThemeWatcher();
|
const themeWatcher = new ThemeWatcher();
|
||||||
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
expect(themeWatcher.getEffectiveTheme()).toBe("dark");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should identify custom dark themes as dark", () => {
|
||||||
|
SettingsStore.getValueAt = makeGetValueAt({ use_system_theme: false, theme: "custom-darkula" });
|
||||||
|
SettingsStore.getValue = makeGetValue({
|
||||||
|
custom_themes: [
|
||||||
|
{
|
||||||
|
name: "darkula",
|
||||||
|
is_dark: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeWatcher = new ThemeWatcher();
|
||||||
|
expect(themeWatcher.getEffectiveTheme()).toBe("custom-darkula");
|
||||||
|
expect(themeWatcher.isUserOnDarkTheme()).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user