diff --git a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts index df41ef4b70..c6af256a5d 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts @@ -56,4 +56,35 @@ test.describe("Appearance user settings tab", () => { // Assert that the font-family value was removed await expect(page.locator("body")).toHaveCSS("font-family", '""'); }); + + test( + "should keep same font and emoji when switching theme", + { tag: "@screenshot" }, + async ({ page, app, user, util }) => { + const roomId = await util.createAndDisplayRoom(); + await app.client.sendMessage(roomId, { body: "Message with 🦡", msgtype: "m.text" }); + + await app.settings.openUserSettings("Appearance"); + const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); + await tab.getByRole("button", { name: "Show advanced" }).click(); + await tab.getByRole("switch", { name: "Use bundled emoji font" }).click(); + await tab.getByRole("switch", { name: "Use a system font" }).click(); + + await app.closeDialog(); + await expect(page).toMatchScreenshot("window-before-switch.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); + + // Switch to dark theme + await app.settings.openUserSettings("Appearance"); + await util.getMatchSystemThemeSwitch().click(); + await util.getDarkTheme().click(); + + await app.closeDialog(); + // Font and emoji should remain the same after theme switch + await expect(page).toMatchScreenshot("window-after-switch.png", { + mask: [page.locator(".mx_MessageTimestamp")], + }); + }, + ); }); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/index.ts b/playwright/e2e/settings/appearance-user-settings-tab/index.ts index 15f2f47888..d27b472017 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/index.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/index.ts @@ -150,9 +150,10 @@ class Helpers { /** * Create and display a room named Test Room */ - async createAndDisplayRoom() { - await this.app.client.createRoom({ name: "Test Room" }); + async createAndDisplayRoom(): Promise { + const roomId = await this.app.client.createRoom({ name: "Test Room" }); await this.app.viewRoomByName("Test Room"); + return roomId; } /** diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png new file mode 100644 index 0000000000..1911271597 Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-after-switch-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png new file mode 100644 index 0000000000..9ca5dee856 Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-before-switch-linux.png differ diff --git a/src/theme.ts b/src/theme.ts index 4cd07f32ab..a0a527c28a 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -26,6 +26,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "./languageHandler"; import SettingsStore from "./settings/SettingsStore"; import ThemeWatcher from "./settings/watchers/ThemeWatcher"; +import { FontWatcher } from "./settings/watchers/FontWatcher"; export const DEFAULT_THEME = "light"; const HIGH_CONTRAST_THEMES: Record = { @@ -126,10 +127,15 @@ export function getOrderedThemes(): ITheme[] { } function clearCustomTheme(): void { - // remove all css variables, we assume these are there because of the custom theme + // remove all css variables (except font and emoji variables), we assume these are there because of the custom theme const inlineStyleProps = Object.values(document.body.style); for (const prop of inlineStyleProps) { - if (typeof prop === "string" && prop.startsWith("--")) { + if ( + typeof prop === "string" && + prop.startsWith("--") && + prop !== FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY && + prop !== FontWatcher.EMOJI_FONT_FAMILY_CUSTOM_PROPERTY + ) { document.body.style.removeProperty(prop); } } diff --git a/test/unit-tests/settings/watchers/FontWatcher-test.tsx b/test/unit-tests/settings/watchers/FontWatcher-test.tsx index 40a34b0328..7e7aab6931 100644 --- a/test/unit-tests/settings/watchers/FontWatcher-test.tsx +++ b/test/unit-tests/settings/watchers/FontWatcher-test.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. */ import { sleep } from "matrix-js-sdk/src/utils"; +import { waitFor } from "jest-matrix-react"; import SettingsStore from "../../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; @@ -155,5 +156,33 @@ describe("FontWatcher", function () { // baseFontSize should be cleared expect(SettingsStore.getValue("baseFontSizeV2")).toBe(0); }); + + it("should trigger migration when dispatched", async () => { + await watcher!.start(); + + await SettingsStore.setValue("baseFontSizeV2", null, SettingLevel.DEVICE, 18); + defaultDispatcher.fire(Action.MigrateBaseFontSize); + + await waitFor(() => { + // 18px - 16px (default browser font size) = 2px + expect(SettingsStore.getValue("fontSizeDelta")).toBe(2); + // baseFontSizeV2 should be cleared + expect(SettingsStore.getValue("baseFontSizeV2")).toBe(0); + }); + }); + }); + + it("should update root font size with positive delta", async () => { + await new FontWatcher().start(); + + defaultDispatcher.dispatch({ + action: Action.UpdateFontSizeDelta, + delta: 2, + }); + + await waitFor(() => { + const rootFontSize = document.querySelector(":root")!.style.fontSize; + expect(rootFontSize).toContain("2px"); + }); }); }); diff --git a/test/unit-tests/theme-test.ts b/test/unit-tests/theme-test.ts index d9622b5c0d..5c55bc7cae 100644 --- a/test/unit-tests/theme-test.ts +++ b/test/unit-tests/theme-test.ts @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import SettingsStore from "../../src/settings/SettingsStore"; +import { FontWatcher } from "../../src/settings/watchers/FontWatcher"; import { enumerateThemes, getOrderedThemes, setTheme } from "../../src/theme"; describe("theme", () => { @@ -223,4 +224,45 @@ describe("theme", () => { ]); }); }); + + describe("clearCustomTheme", () => { + beforeEach(() => { + // Reset document state + document.body.style.cssText = ""; + document.head.querySelectorAll("style[title^='custom-theme-']").forEach((el) => el.remove()); + }); + + it("should not remove font family custom properties", async () => { + // Mock theme elements + const lightTheme = { + dataset: { mxTheme: "light" }, + disabled: true, + href: "fake URL", + onload: (): void => void 0, + } as unknown as HTMLStyleElement; + + const removePropertySpy = jest.fn(); + const styleObject = { + 0: FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY, + 1: FontWatcher.EMOJI_FONT_FAMILY_CUSTOM_PROPERTY, + 2: "--custom-color", + length: 3, + removeProperty: removePropertySpy, + }; + jest.spyOn(document.body, "style", "get").mockReturnValue(styleObject as any); + jest.spyOn(document, "querySelectorAll").mockReturnValue([lightTheme] as any); + + // Trigger clearCustomTheme via setTheme + await new Promise((resolve) => { + setTheme("light").then(resolve); + lightTheme.onload!({} as Event); + }); + + // Check that font properties were NOT removed + expect(removePropertySpy).not.toHaveBeenCalledWith(FontWatcher.FONT_FAMILY_CUSTOM_PROPERTY); + expect(removePropertySpy).not.toHaveBeenCalledWith(FontWatcher.EMOJI_FONT_FAMILY_CUSTOM_PROPERTY); + // But custom color should be removed + expect(removePropertySpy).toHaveBeenCalledWith("--custom-color"); + }); + }); });