fix(settings): stop a theme picked earlier staying previewed in the Settings window (#2343)

The theme row only re-previewed a staged theme that differed from the saved one, and only when the list closed, so re-picking the saved theme quickly or pressing Reset left the earlier pick painted in the Settings window. It now shows its staged theme, or the saved one, whenever that changes and whenever the list closes.

Co-authored-by: ntbang0901 <[email protected]>
Цей коміт міститься в:
ntbang0901 authored and GitHub committed 2026-11-10 00:03:00 +08:00
1 джерело 394e9919c7
коміт 20be9ff646
2 files changed
+91 -1

No files matched your search

+82
Переглянути файл
@@ -0,0 +1,82 @@
import { act, fireEvent, render, screen, within } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useProject } from "@/platform/project";
import { applyThemeAttributes, currentThemeId, useThemeOverride } from "@/workbench/theme";
import { THEME_PREVIEW_DELAY } from "@/workbench/quickSwitch";
import { DEFAULT_SETTINGS } from "../model/schema";
import { openSettings, resetPage } from "../model/staging";
import { PageContext } from "../components/context";
import { BEHAVIOR_PAGES } from "../pages/behavior";
const root = document.documentElement;
const shown = () => [root.dataset.theme, root.dataset.colorTheme ?? null];
function renderThemeRow() {
const page = BEHAVIOR_PAGES.find((p) => p.id === "appearance")!;
const option = page.sections!.flatMap((s) => s.options).find((o) => o.kind === "custom" && o.label === "Theme");
if (option?.kind !== "custom") throw new Error("no Theme row");
const Row = option.component;
return render(
<PageContext.Provider value={{ pageId: page.id, scope: "global" }}>
<Row />
</PageContext.Provider>,
);
}
const pick = (label: string) => {
fireEvent.click(screen.getByRole("button", { name: "Theme" }));
fireEvent.click(within(screen.getByRole("listbox")).getByText(label));
};
beforeEach(() => {
vi.useFakeTimers();
useThemeOverride.setState({ theme: null });
useProject.setState({ settings: structuredClone(DEFAULT_SETTINGS), project: null });
applyThemeAttributes(currentThemeId());
openSettings("appearance");
});
afterEach(() => {
vi.useRealTimers();
useThemeOverride.setState({ theme: null });
applyThemeAttributes(currentThemeId());
});
describe("Settings › Appearance › Theme preview", () => {
it("shows a picked theme in this window until Apply", () => {
renderThemeRow();
pick("Solarized Dark");
expect(shown()).toEqual(["dark", "solarized-dark"]);
});
it("goes back to the selected theme when it is picked again before the hover preview shows", () => {
renderThemeRow();
expect(shown()).toEqual(["light", null]);
pick("Solarized Dark");
expect(shown()).toEqual(["dark", "solarized-dark"]);
// Clicked quicker than the hover delay: the list itself never previewed anything.
act(() => vi.advanceTimersByTime(THEME_PREVIEW_DELAY / 2));
pick("Islands Light");
act(() => vi.advanceTimersByTime(THEME_PREVIEW_DELAY));
expect(screen.getByRole("button", { name: "Theme" })).toHaveTextContent("Islands Light");
expect(shown()).toEqual(["light", null]);
});
it("goes back to the selected theme after hovering it long enough", () => {
renderThemeRow();
pick("Solarized Dark");
fireEvent.click(screen.getByRole("button", { name: "Theme" }));
fireEvent.pointerEnter(within(screen.getByRole("listbox")).getByText("Islands Light"));
act(() => vi.advanceTimersByTime(THEME_PREVIEW_DELAY));
fireEvent.click(within(screen.getByRole("listbox")).getByText("Islands Light"));
expect(shown()).toEqual(["light", null]);
});
it("goes back to the selected theme when the page is reset", () => {
renderThemeRow();
pick("Solarized Dark");
act(() => resetPage("appearance"));
expect(screen.getByRole("button", { name: "Theme" })).toHaveTextContent("Islands Light");
expect(shown()).toEqual(["light", null]);
});
});
+9 -1
Переглянути файл
@@ -53,6 +53,9 @@ function Indented({ children }: { children: ReactNode }) {
// ---------------------------------------------------------------------------
// Appearance
/** Shows the theme picked in Settings in this window until Apply, or the selected one again. */
const showPicked = (id: ConcreteTheme) => previewTheme(id !== currentThemeChoice() ? id : null);
function ThemeRow() {
const [theme, setTheme] = useSetting<ThemeId>("appearance.theme");
const [sync, setSync] = useSetting<boolean>("appearance.syncWithOs");
@@ -72,11 +75,16 @@ function ThemeRow() {
},
[],
);
// The staged theme can also change without the list, e.g. by Reset.
useEffect(() => {
if (previewed.current) showPicked(shown);
}, [shown]);
const preview = (id: ConcreteTheme | null) => {
previewed.current = true;
if (id !== null) return picker.preview(id);
// The picker takes back only its own hover preview, not a theme picked here earlier.
picker.restore();
if (shown !== currentThemeChoice()) previewTheme(shown);
showPicked(shown);
};
return (
<Row label="Theme">