import { expect, test, type Page } from '@playwright/test' const user = { id: 42, username: 'fictional-theme', role: 'learner' } // The palette is one indirection deep: a CSS variable that references itself silently resolves to // nothing and every surface loses its colour. These checks read the values back from the browser. const palette = ['--bg', '--surface', '--surface-input', '--text', '--text-muted', '--border', '--accent', '--accent-marker', '--notice-bg', '--status-ready-bg', '--status-read-bg', '--status-failed-bg', '--word-learning-bg', '--saved', '--sense-bg', '--el-color-primary'] async function mockApi(page: Page): Promise { await page.route('**/api/v1/**', async route => { const path = new URL(route.request().url()).pathname let data: unknown = null if (path === '/api/v1/login') data = { token: 'fictional-session', user } else if (path === '/api/v1/me') data = user else if (path === '/api/v1/space') data = { ownerId: user.id, language: 'en' } else if (path === '/api/v1/books') data = { items: [] } await route.fulfill({ json: { code: 200, data } }) }) } async function login(page: Page): Promise { await page.goto('/') await page.getByLabel('账号').fill(user.username) await page.getByLabel('密码', { exact: true }).fill('fictional-password') await page.getByRole('button', { name: '登录', exact: true }).click() await expect(page.getByRole('heading', { name: '我的书库' })).toBeVisible() } test('every palette colour resolves, in both themes', async ({ page }) => { await mockApi(page) await login(page) const read = () => page.evaluate(names => { const root = getComputedStyle(document.documentElement) const values: Record = {} for (const name of names) values[name] = root.getPropertyValue(name).trim() // The palette is declared on :root, so that is where the painted surface is read. const html = getComputedStyle(document.documentElement) return { values, body: html.backgroundColor, text: getComputedStyle(document.body).color, } }, palette) const light = await read() for (const name of palette) { expect(light.values[name], `${name} must resolve in the light theme`).not.toBe('') } // The page really paints with them: an unresolved background would be transparent. expect(light.body).not.toBe('rgba(0, 0, 0, 0)') expect(light.body).not.toBe('transparent') expect(light.text).not.toBe(light.body) await page.screenshot({ path: '../.local/evidence/issue14-theme-light.png' }) await page.getByTestId('display-trigger').click() await page.getByRole('menu').getByText('深色', { exact: true }).click() await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') const dark = await read() for (const name of palette) { expect(dark.values[name], `${name} must resolve in the dark theme`).not.toBe('') } // The dark theme is a different surface, not the same one relabelled. expect(dark.values['--bg']).not.toBe(light.values['--bg']) expect(dark.body).not.toBe(light.body) expect(dark.text).not.toBe(dark.body) // Element Plus follows the same choice, so its components do not stay on a white card. const epBackground = await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--el-bg-color').trim()) expect(epBackground).not.toBe('') await page.screenshot({ path: '../.local/evidence/issue14-theme-dark.png' }) // Back to light by hand, and the surface returns to the original value. await page.getByTestId('display-trigger').click() await page.getByRole('menu').getByText('浅色', { exact: true }).click() expect((await read()).body).toBe(light.body) })