feat(web): add install metadata
This commit is contained in:
27
apps/web/tests/pwa-manifest.e2e.ts
Normal file
27
apps/web/tests/pwa-manifest.e2e.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join } from 'node:path'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
const DIST_ROOT = fileURLToPath(new URL('../dist', import.meta.url))
|
||||
|
||||
it('ships install metadata with the built web application', async () => {
|
||||
const index = await readFile(join(DIST_ROOT, 'index.html'), 'utf8')
|
||||
expect(index).toContain('<link rel="manifest" href="/manifest.webmanifest" />')
|
||||
|
||||
const manifest: unknown = JSON.parse(await readFile(join(DIST_ROOT, 'manifest.webmanifest'), 'utf8'))
|
||||
expect(manifest).toEqual({
|
||||
id: '/',
|
||||
name: 'DeepSeek Harness',
|
||||
short_name: 'DSH',
|
||||
start_url: '/',
|
||||
scope: '/',
|
||||
display: 'fullscreen',
|
||||
icons: [{
|
||||
src: '/favicon.svg',
|
||||
sizes: 'any',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'any',
|
||||
}],
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user