|
5 | 5 | * |
6 | 6 | * Copyright Oxide Computer Company |
7 | 7 | */ |
| 8 | +import type { Page } from '@playwright/test' |
| 9 | + |
8 | 10 | import { expect, test } from './utils' |
9 | 11 |
|
| 12 | +/** Seed theme preference into localStorage before any page loads. */ |
| 13 | +async function seedTheme(page: Page, theme: string) { |
| 14 | + await page.addInitScript((t) => { |
| 15 | + localStorage.setItem( |
| 16 | + 'theme-preference', |
| 17 | + JSON.stringify({ state: { theme: t }, version: 0 }) |
| 18 | + ) |
| 19 | + }, theme) |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * Block the app entry so React never boots. This isolates theme-init.js, |
| 24 | + * letting us test the pre-hydration theme. The #root empty check in tests |
| 25 | + * ensures this block is still working. |
| 26 | + */ |
| 27 | +async function blockReact(page: Page) { |
| 28 | + await page.route('**/app/main.tsx*', (route) => route.abort('blockedbyclient')) |
| 29 | +} |
| 30 | + |
| 31 | +test.describe('theme-init.js (pre-hydration)', () => { |
| 32 | + test('defaults to dark with no stored preference', async ({ page }) => { |
| 33 | + await blockReact(page) |
| 34 | + await page.goto('/projects', { waitUntil: 'domcontentloaded' }) |
| 35 | + await expect(page.locator('#root')).toBeEmpty() |
| 36 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') |
| 37 | + }) |
| 38 | + |
| 39 | + test('respects stored light preference', async ({ page }) => { |
| 40 | + await seedTheme(page, 'light') |
| 41 | + await blockReact(page) |
| 42 | + await page.goto('/projects', { waitUntil: 'domcontentloaded' }) |
| 43 | + await expect(page.locator('#root')).toBeEmpty() |
| 44 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') |
| 45 | + }) |
| 46 | + |
| 47 | + test('respects stored dark preference', async ({ page }) => { |
| 48 | + await seedTheme(page, 'dark') |
| 49 | + await blockReact(page) |
| 50 | + await page.goto('/projects', { waitUntil: 'domcontentloaded' }) |
| 51 | + await expect(page.locator('#root')).toBeEmpty() |
| 52 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') |
| 53 | + }) |
| 54 | + |
| 55 | + test('system preference resolves to emulated color scheme', async ({ page }) => { |
| 56 | + await seedTheme(page, 'system') |
| 57 | + await blockReact(page) |
| 58 | + |
| 59 | + await page.emulateMedia({ colorScheme: 'light' }) |
| 60 | + await page.goto('/projects', { waitUntil: 'domcontentloaded' }) |
| 61 | + await expect(page.locator('#root')).toBeEmpty() |
| 62 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') |
| 63 | + |
| 64 | + await page.emulateMedia({ colorScheme: 'dark' }) |
| 65 | + await page.goto('/projects', { waitUntil: 'domcontentloaded' }) |
| 66 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') |
| 67 | + }) |
| 68 | + |
| 69 | + test('forces dark on auth pages regardless of preference', async ({ page }) => { |
| 70 | + await seedTheme(page, 'light') |
| 71 | + await blockReact(page) |
| 72 | + |
| 73 | + for (const path of ['/login/default-silo/saml/mock-idp', '/device/verify']) { |
| 74 | + await page.goto(path, { waitUntil: 'domcontentloaded' }) |
| 75 | + await expect(page.locator('#root')).toBeEmpty() |
| 76 | + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') |
| 77 | + } |
| 78 | + }) |
| 79 | +}) |
| 80 | + |
| 81 | +test('Login and device pages force dark theme even when preference is light', async ({ |
| 82 | + page, |
| 83 | +}) => { |
| 84 | + // Set theme to light via the UI |
| 85 | + await page.goto('/projects') |
| 86 | + await page.getByRole('button', { name: 'User menu' }).click() |
| 87 | + await page.getByRole('menuitem', { name: 'Theme' }).click() |
| 88 | + await page.getByRole('menuitemradio', { name: 'Light' }).click() |
| 89 | + await page.keyboard.press('Escape') |
| 90 | + |
| 91 | + const html = page.locator('html') |
| 92 | + await expect(html).toHaveAttribute('data-theme', 'light') |
| 93 | + |
| 94 | + await page.goto('/login/default-silo/saml/mock-idp') |
| 95 | + await expect(html).toHaveAttribute('data-theme', 'dark') |
| 96 | + |
| 97 | + await page.goto('/device/verify') |
| 98 | + await expect(html).toHaveAttribute('data-theme', 'dark') |
| 99 | +}) |
| 100 | + |
10 | 101 | test('Serial console terminal updates colors on theme change', async ({ page }) => { |
11 | 102 | await page.goto('/projects/mock-project/instances/db1/serial-console') |
12 | 103 |
|
|
0 commit comments