feat: add theme mode dropdown (#25183)

## Summary

- Wire the Appearance settings page to the new theme mode dropdown and
sync or single theme selectors.
- Update Appearance page tests and stories for theme mode behavior.
- Update the user settings e2e test to exercise single theme selection.

## Dependencies

- Depends on #25076, #25180, #25181, and #25182.
- This PR targets helper branch `pr25077/05-theme-mode-dropdown-base`,
which contains dependency commits only, so this PR diff stays focused on
final dropdown wiring. Rebase and retarget after the dependency PRs
merge.

## Validation

- `pnpm -C site exec vitest run --project=unit
src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx
src/theme/themeMode.test.ts src/api/queries/users.test.ts`
- `pnpm -C site lint:types`
- `pnpm -C site storybook:ci`
- `pnpm -C site build`
- `pnpm -C site playwright:test -- e2e/tests/users/userSettings.spec.ts`
- Pre-commit hook passed on the branch commit.
This commit is contained in:
Jaayden Halko
2026-05-15 16:15:06 +01:00
committed by GitHub
parent 6d7fb07f4c
commit 2c18e07e39
7 changed files with 826 additions and 375 deletions
+21 -13
View File
@@ -12,15 +12,19 @@ const rootClassNames = async (page: Page) => {
return page.locator("html").evaluate((it) => Array.from(it.classList));
};
// Assert the light theme without rejecting unrelated root classes.
const expectLightThemeClasses = (classes: string[]) => {
const className = "light";
expect(classes).toContain(className);
for (const themeClassName of CONCRETE_THEMES.filter(
(it) => it !== className,
)) {
expect(classes).not.toContain(themeClassName);
}
const expectLightThemeClasses = async (page: Page) => {
await expect(async () => {
const classes = await rootClassNames(page);
const className = "light";
// Assert the light theme without rejecting unrelated root classes.
expect(classes).toContain(className);
for (const themeClassName of CONCRETE_THEMES.filter(
(it) => it !== className,
)) {
expect(classes).not.toContain(themeClassName);
}
}).toPass({ timeout: 10_000 });
};
test("adjust user theme preference", async ({ page }) => {
@@ -28,14 +32,18 @@ test("adjust user theme preference", async ({ page }) => {
await page.goto("/settings/appearance", { waitUntil: "domcontentloaded" });
await page.getByText("Light", { exact: true }).click();
await expect(page.getByLabel("Light")).toBeChecked();
await page.getByRole("combobox", { name: /theme mode/i }).click();
await page.getByRole("option", { name: /single theme/i }).click();
expectLightThemeClasses(await rootClassNames(page));
const singleThemeGroup = page.getByRole("group", { name: "Theme" });
await expect(singleThemeGroup).toBeVisible();
await singleThemeGroup.getByText("Light default", { exact: true }).click();
await expectLightThemeClasses(page);
await page.goto("/", { waitUntil: "domcontentloaded" });
// Make sure the page is still using the light theme after reloading and
// navigating away from the settings page.
expectLightThemeClasses(await rootClassNames(page));
await expectLightThemeClasses(page);
});