Files
directory/apps/web/e2e/theme-toggle.e2e.ts
eleith 4205dfa072
ci/woodpecker/pr/web Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
Simplify E2E coverage
2026-10-28 09:52:36 -07:00

130 lines
5.0 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test';
import { credentialsAuth, password, username } from './support/configuration.js';
import { navigateQuery } from './support/navigation.js';
import { startServer, type TestServer } from './support/server.js';
const configuration = `site:
title: Theme fixture
links:
calendar:
title: Household calendar
url: https://calendar.example.test/
`;
let server: TestServer;
test.beforeAll(async () => {
server = await startServer({ configuration });
await server.ready();
});
test.afterAll(async () => {
expect(await server.stop()).toEqual({ code: 0, signal: null });
});
async function expectTheme(page: Page, preference: string, applied: 'light' | 'dark') {
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', preference);
await expect(page.locator('html')).toHaveAttribute('data-theme', applied);
await expect(page.locator('html')).toHaveCSS('color-scheme', applied);
}
test('keyboard theme selection follows System, persists, and survives navigation', async ({
page
}) => {
await page.emulateMedia({ colorScheme: 'light' });
await page.goto(server.url);
const toggle = page.getByRole('button', { name: /theme/ });
await expect(toggle).toBeEnabled();
await expectTheme(page, 'system', 'light');
await page.emulateMedia({ colorScheme: 'dark' });
await expectTheme(page, 'system', 'dark');
await page.emulateMedia({ colorScheme: 'light' });
await expectTheme(page, 'system', 'light');
await page.keyboard.press('Tab');
await expect(page.getByRole('searchbox', { name: 'Search', exact: true })).toBeFocused();
await page.keyboard.press('Tab');
await expect(toggle).toBeFocused();
await page.keyboard.press('Enter');
await expectTheme(page, 'dark', 'dark');
await page.keyboard.press('Space');
await expectTheme(page, 'light', 'light');
await page.keyboard.press('Enter');
await expectTheme(page, 'system', 'light');
await toggle.click();
await page.reload();
await expect(toggle).toBeEnabled();
await expectTheme(page, 'dark', 'dark');
await page.emulateMedia({ colorScheme: 'dark' });
await page.emulateMedia({ colorScheme: 'light' });
await expectTheme(page, 'dark', 'dark');
await navigateQuery(page, '/?q=calendar');
await expect(page.getByRole('link', { name: 'Household calendar', exact: true })).toBeVisible();
await expectTheme(page, 'dark', 'dark');
});
test('saved theme applies before hydration and CSP blocks untrusted inline scripts', async ({
page
}) => {
await page.emulateMedia({ colorScheme: 'dark' });
await page.addInitScript(() => localStorage.setItem('directory.theme', 'light'));
await page.route('**/_app/**/*.js', (route) => route.abort());
const response = await page.goto(server.url);
const policy = response!.headers()['content-security-policy'];
expect(policy).toContain("object-src 'none'");
expect(policy).toContain("frame-ancestors 'none'");
await expectTheme(page, 'light', 'light');
await expect(page.getByRole('button', { name: /theme/ })).toBeDisabled();
await page.evaluate(() => {
const script = document.createElement('script');
script.textContent = 'document.documentElement.dataset.untrustedScript = "ran"';
document.head.append(script);
});
await expect(page.locator('html')).not.toHaveAttribute('data-untrusted-script');
});
test('unavailable storage leaves theme selection usable', async ({ page }) => {
await page.emulateMedia({ colorScheme: 'light' });
await page.addInitScript(() => {
for (const method of ['getItem', 'setItem'] as const) {
Storage.prototype[method] = () => {
throw new DOMException('Blocked', 'SecurityError');
};
}
});
await page.goto(server.url);
const toggle = page.getByRole('button', { name: /theme/ });
await expect(toggle).toBeEnabled();
await expectTheme(page, 'system', 'light');
await toggle.click();
await expectTheme(page, 'dark', 'dark');
await toggle.click();
await expectTheme(page, 'light', 'light');
await toggle.click();
await page.emulateMedia({ colorScheme: 'dark' });
await expectTheme(page, 'system', 'dark');
});
test('saved theme survives sign-in and sign-out', async ({ page }) => {
const app = await startServer({ configuration: configuration + credentialsAuth });
try {
await app.ready();
await page.emulateMedia({ colorScheme: 'light' });
await page.goto(app.url);
await expect(page).toHaveURL((url) => url.pathname === '/signin');
await page.evaluate(() => localStorage.setItem('directory.theme', 'dark'));
await page.reload();
await expectTheme(page, 'dark', 'dark');
await page.getByLabel('Username').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in', exact: true }).click();
await expect(page.getByRole('button', { name: 'Sign out' })).toBeVisible();
await expectTheme(page, 'dark', 'dark');
await page.getByRole('button', { name: 'Sign out' }).click();
await expect(page).toHaveURL((url) => url.pathname === '/signin');
await expectTheme(page, 'dark', 'dark');
} finally {
expect(await app.stop()).toEqual({ code: 0, signal: null });
}
});