Keep the theme on the account, not in the browser
- users.theme and users.theme_mode, added by migrate(); GET /api/me returns them and PATCH /api/me saves them, refusing anything but a plain name and light/dark/auto, since index() writes them into the page's <html> tag. - The page arrives with data-theme and data-choice already on <html> (and data-mode unless Auto), so it is drawn in the account's theme from the start. - A theme a browser kept in localStorage goes up to the account once, the first time an account with none loads the page. - Saves go one at a time, each with the choice as it stands: sent all at once, a quick run through the list could land out of order and keep a theme passed on the way. The browser test caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,8 +63,12 @@ test('Settings picks a theme and, where it has both, light, dark or Auto', async
|
||||
await page.locator('#stheme').selectOption('paper');
|
||||
await expect(page.locator('#smode')).toBeHidden();
|
||||
await expect.poll(bg).toBe('rgb(242, 238, 222)'); // #F2EEDE
|
||||
// The save that says Classic: the ones before it may still be answering.
|
||||
const saved = page.waitForResponse(r => r.url().endsWith('/api/me') && r.request().method() === 'PATCH'
|
||||
&& r.request().postDataJSON().theme === 'classic');
|
||||
await page.locator('#stheme').selectOption('classic');
|
||||
await expect(page.locator('#smode')).toBeHidden();
|
||||
await saved; // kept on the account, not the browser
|
||||
await page.reload();
|
||||
await expect.poll(root).toEqual(['classic', 'light']);
|
||||
// The 2004 Mac app set its type in Lucida Grande.
|
||||
@@ -79,11 +83,46 @@ test('Settings picks a theme and, where it has both, light, dark or Auto', async
|
||||
await expect.poll(bg).toBe('rgb(46, 52, 64)'); // nord0
|
||||
});
|
||||
|
||||
test('a theme saved before there was light and dark carries over', async ({ page }) => {
|
||||
await page.evaluate(() => { localStorage.setItem('ipx.theme', 'light'); localStorage.removeItem('ipx.mode'); });
|
||||
await page.reload();
|
||||
test('the theme is kept on the account, and follows it to another browser', async ({ page, browser }) => {
|
||||
await page.locator('#prefs').click();
|
||||
const saved = page.waitForResponse(r => r.url().endsWith('/api/me') && r.request().method() === 'PATCH');
|
||||
await page.locator('#stheme').selectOption('flatremix');
|
||||
expect((await saved).status()).toBe(204);
|
||||
const saved2 = page.waitForResponse(r => r.url().endsWith('/api/me') && r.request().method() === 'PATCH');
|
||||
await page.locator('#smode').selectOption('light');
|
||||
await saved2;
|
||||
|
||||
// Another browser: nothing in its localStorage, and the page still arrives in the theme,
|
||||
// written onto <html> by the server rather than set once the script has run.
|
||||
const other = await browser.newContext();
|
||||
const p2 = await other.newPage();
|
||||
const res = await p2.goto(`/?token=${TOKEN}`);
|
||||
expect(await res.text()).toContain('data-theme=flatremix data-choice=light data-mode=light');
|
||||
expect(await p2.evaluate(() => [document.documentElement.dataset.theme, document.documentElement.dataset.mode]))
|
||||
.toEqual(['flatremix', 'light']);
|
||||
await other.close();
|
||||
});
|
||||
|
||||
test('a theme this browser kept before themes were on the account goes up to it once', async ({ browser }) => {
|
||||
// A new account, made by the proxy header on first sight, so it has no theme of its own yet.
|
||||
const who = `theme-${Date.now()}@example.com`;
|
||||
const ctx = await browser.newContext({ extraHTTPHeaders: { 'X-Test-User': who } });
|
||||
// From before light and dark: ipx.theme alone, 'light' meaning Modern, light.
|
||||
await ctx.addInitScript(() => { localStorage.setItem('ipx.theme', 'light'); localStorage.removeItem('ipx.mode'); });
|
||||
const page = await ctx.newPage();
|
||||
const saved = page.waitForResponse(r => r.url().endsWith('/api/me') && r.request().method() === 'PATCH');
|
||||
await page.goto('/');
|
||||
expect(await page.evaluate(() => [document.documentElement.dataset.theme, document.documentElement.dataset.mode]))
|
||||
.toEqual(['modern', 'light']);
|
||||
expect((await saved).request().postDataJSON()).toEqual({ theme: 'modern', mode: 'light' });
|
||||
await ctx.close();
|
||||
|
||||
// Anywhere else now, it comes from the account.
|
||||
const fresh = await browser.newContext({ extraHTTPHeaders: { 'X-Test-User': who } });
|
||||
const p2 = await fresh.newPage();
|
||||
const res = await p2.goto('/');
|
||||
expect(await res.text()).toContain('data-theme=modern data-choice=light');
|
||||
await fresh.close();
|
||||
});
|
||||
|
||||
test('settings opens and saves the global schedule', async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user