|
|
|
@@ -1,7 +1,8 @@
|
|
|
|
|
// Web e2e scenarios: the settings surface — the modal shell (trigger, nav,
|
|
|
|
|
// section switching, both close paths), the Appearance preference row (the
|
|
|
|
|
// real theme gesture — click 深色 and the whole cascade runs: ThemeService preference -> localStorage dsh.theme
|
|
|
|
|
// -> theme/change -> ui-layout's presenter -> body attribute -> alias token)
|
|
|
|
|
// -> theme/change -> ui-layout's presenter -> body attribute -> alias token +
|
|
|
|
|
// browser theme-color metadata)
|
|
|
|
|
// the Language row (settings-scoped localization + persisted dsh.locale),
|
|
|
|
|
// the busy-state Enter preference, plus Permission as the persisted default
|
|
|
|
|
// for subsequently created sessions.
|
|
|
|
@@ -154,17 +155,37 @@ describe('web e2e: settings modal and General preferences', () => {
|
|
|
|
|
|
|
|
|
|
it('flips the theme through the Appearance cubes and persists across reload', async () => {
|
|
|
|
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-appearance'))
|
|
|
|
|
const readState = async (): Promise<{ attr: boolean; token: string; stored: string | null }> =>
|
|
|
|
|
await page.evaluate(() => ({
|
|
|
|
|
interface ThemeState {
|
|
|
|
|
attr: boolean
|
|
|
|
|
background: string
|
|
|
|
|
stored: string | null
|
|
|
|
|
themeColor: string | null
|
|
|
|
|
themeColorCount: number
|
|
|
|
|
token: string
|
|
|
|
|
}
|
|
|
|
|
const readState = async (): Promise<ThemeState> => await page.evaluate(() => {
|
|
|
|
|
const metas = document.head.querySelectorAll<HTMLMetaElement>('meta[name="theme-color"]')
|
|
|
|
|
const computed = getComputedStyle(document.body)
|
|
|
|
|
return {
|
|
|
|
|
attr: document.body.hasAttribute('data-ds-dark-theme'),
|
|
|
|
|
token: getComputedStyle(document.body).getPropertyValue('--dsw-alias-bg-base').trim(),
|
|
|
|
|
background: computed.backgroundColor,
|
|
|
|
|
stored: localStorage.getItem('dsh.theme'),
|
|
|
|
|
}))
|
|
|
|
|
themeColor: metas[0]?.content ?? null,
|
|
|
|
|
themeColorCount: metas.length,
|
|
|
|
|
token: computed.getPropertyValue('--dsw-alias-bg-base').trim(),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
const expectThemeColorSynchronized = (state: ThemeState): void => {
|
|
|
|
|
expect(state.themeColorCount).toBe(1)
|
|
|
|
|
expect(state.background).not.toBe('rgba(0, 0, 0, 0)')
|
|
|
|
|
expect(state.themeColor).toBe(state.background)
|
|
|
|
|
}
|
|
|
|
|
// Pin the OS scheme to light so the default `system` preference resolves
|
|
|
|
|
// light and the dark flip below is unambiguously the gesture's doing.
|
|
|
|
|
await page.emulateMedia({ colorScheme: 'light' })
|
|
|
|
|
const light = await readState()
|
|
|
|
|
expect(light.attr).toBe(false)
|
|
|
|
|
expectThemeColorSynchronized(light)
|
|
|
|
|
|
|
|
|
|
await page.getByRole('button', { name: '设置', exact: true }).click()
|
|
|
|
|
const dialog = page.getByRole('dialog', { name: '设置' })
|
|
|
|
@@ -179,6 +200,7 @@ describe('web e2e: settings modal and General preferences', () => {
|
|
|
|
|
expect(dark.attr).toBe(true)
|
|
|
|
|
expect(dark.stored).toBe('dark')
|
|
|
|
|
expect(dark.token).not.toBe(light.token)
|
|
|
|
|
expectThemeColorSynchronized(dark)
|
|
|
|
|
await page.keyboard.press('Escape')
|
|
|
|
|
|
|
|
|
|
// Reload: the preference survives boot (restore + presenter initial apply).
|
|
|
|
@@ -190,6 +212,7 @@ describe('web e2e: settings modal and General preferences', () => {
|
|
|
|
|
const reloaded = await readState()
|
|
|
|
|
expect(reloaded.attr).toBe(true)
|
|
|
|
|
expect(reloaded.stored).toBe('dark')
|
|
|
|
|
expectThemeColorSynchronized(reloaded)
|
|
|
|
|
|
|
|
|
|
// `system` follows the emulated OS scheme (dark stays dark, light clears).
|
|
|
|
|
await page.getByRole('button', { name: '设置', exact: true }).click()
|
|
|
|
@@ -197,12 +220,15 @@ describe('web e2e: settings modal and General preferences', () => {
|
|
|
|
|
await systemCube.click()
|
|
|
|
|
await expect.poll(() => systemCube.getAttribute('aria-pressed'), { timeout: 5_000 }).toBe('true')
|
|
|
|
|
await expect.poll(async () => (await readState()).attr, { timeout: 5_000 }).toBe(false)
|
|
|
|
|
expectThemeColorSynchronized(await readState())
|
|
|
|
|
await page.emulateMedia({ colorScheme: 'dark' })
|
|
|
|
|
await expect.poll(async () => (await readState()).attr, { timeout: 5_000 }).toBe(true)
|
|
|
|
|
expectThemeColorSynchronized(await readState())
|
|
|
|
|
// Restore for the specs that follow: light preference beats the emulated
|
|
|
|
|
// dark OS scheme, leaving the shared page in the light default.
|
|
|
|
|
await page.getByRole('dialog', { name: '设置' }).getByRole('button', { name: '浅色' }).click()
|
|
|
|
|
await expect.poll(async () => (await readState()).attr, { timeout: 5_000 }).toBe(false)
|
|
|
|
|
expectThemeColorSynchronized(await readState())
|
|
|
|
|
await page.keyboard.press('Escape')
|
|
|
|
|
expect(tripwire.pageErrors).toEqual([])
|
|
|
|
|
}, 90_000)
|
|
|
|
|