feat: 增加更新页

This commit is contained in:
Wxw-Gu
2026-08-25 21:07:25 +08:00
parent eec6286a58
commit 9dee0bbd3c
28 changed files with 1079 additions and 135 deletions
+56
View File
@@ -340,6 +340,62 @@ test('SETTINGS-02 basic settings controls fit a narrow viewport and keep their s
}
})
test('UPDATE-01 simulated startup update navigates to live progress and never exits on install', async () => {
const fixture = await launchTestApp({ updateSimulation: true })
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.0.0' })
await expect(dialog).toBeVisible()
await dialog.getByRole('button', { name: '立即下载' }).click()
await expect(fixture.page.getByRole('heading', { name: '关于' })).toBeVisible()
await expect(fixture.page.getByRole('button', { name: '关于' })).toHaveClass(/active/)
await expect(fixture.page.getByText('正在下载 v2.0.0')).toBeVisible()
await expect
.poll(async () => {
const value = await fixture.page
.getByRole('progressbar', { name: /更新下载进度/ })
.getAttribute('aria-valuenow')
return Number(value)
})
.toBeGreaterThan(0)
await expect(fixture.page.getByText(/MB \/ 60\.0 MB/)).toBeVisible()
await expect(fixture.page.getByText(/MB\/s/)).toBeVisible()
await expect(fixture.page.getByText('v2.0.0 已准备完成')).toBeVisible({ timeout: 5_000 })
await fixture.page.getByRole('button', { name: '立即重启更新' }).click()
await expect(
fixture.page.getByText('开发模拟模式:更新安装动作已模拟,未实际退出应用。')
).toBeVisible()
await expect(fixture.page.getByRole('heading', { name: '关于' })).toBeVisible()
expect(pageErrors).toEqual([])
} finally {
await fixture.close()
}
})
test('UPDATE-02 unsigned macOS update opens the latest release without downloading', async () => {
const fixture = await launchTestApp({ unsignedMacUpdate: true })
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.2.3' })
await expect(dialog).toBeVisible()
await expect(dialog.getByText('有新版本可用,前往 GitHub 下载最新版本。')).toBeVisible()
await dialog.getByRole('button', { name: '前往下载' }).click()
const openedUrl = await fixture.page.evaluate(() =>
window.electron.ipcRenderer.invoke('app-update:getOpenedDownloadUrl')
)
expect(openedUrl).toBe('https://github.com/Wxw-Gu/TraceMemo/releases/latest')
await expect(fixture.page.getByRole('progressbar')).toHaveCount(0)
expect(pageErrors).toEqual([])
} finally {
await fixture.close()
}
})
test('SETTINGS-03 database key actions keep destructive confirmation keyboard-safe', async () => {
const fixture = await launchTestApp()
const pageErrors: Error[] = []
+79 -3
View File
@@ -15,6 +15,8 @@ const VALID_KEY = 'a'.repeat(64)
const imageData = `data:image/png;base64,${fs.readFileSync(path.join(root, 'resources/icon.png')).toString('base64')}`
const voiceData = 'UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA='
const configuredNow = Number(process.env.WXE_E2E_NOW_MS)
const updateSimulation = process.env.WXE_E2E_UPDATE_SIMULATION === '1'
const unsignedMacUpdate = process.env.WXE_E2E_UNSIGNED_MAC_UPDATE === '1'
const fixtureNowMs =
Number.isFinite(configuredNow) && configuredNow > 0 ? configuredNow : Date.now()
@@ -801,7 +803,83 @@ handle('accounts:discover', (inputPath) =>
]
}
)
handle('app-update:getState', () => ({ status: 'idle', currentVersion: '2.2.0' }))
let appUpdateState = updateSimulation
? {
status: 'available',
currentVersion: '1.9.0',
delivery: 'automatic',
version: '2.0.0',
source: 'startup',
isSimulation: true
}
: unsignedMacUpdate
? {
status: 'available',
currentVersion: '2.2.2',
delivery: 'release-page',
version: '2.2.3',
source: 'startup'
}
: { status: 'idle', currentVersion: '2.2.0', delivery: 'automatic' }
let openedUpdateDownloadUrl = ''
handle('app-update:getState', () => appUpdateState)
handle('app-update:openDownloadPage', () => {
openedUpdateDownloadUrl = 'https://github.com/Wxw-Gu/TraceMemo/releases/latest'
return { success: true }
})
handle('app-update:getOpenedDownloadUrl', () => openedUpdateDownloadUrl)
handle('app-update:download', async () => {
if (!updateSimulation) return { success: true, state: appUpdateState }
const total = 60 * 1024 * 1024
const steps = [0, 5, 12, 21, 33, 46, 58, 69, 78, 86, 93, 97, 100]
let previousTransferred = 0
appUpdateState = {
...appUpdateState,
status: 'downloading',
percent: 0,
transferred: 0,
total,
bytesPerSecond: 0
}
for (const percent of steps.slice(1)) {
await new Promise((resolve) => setTimeout(resolve, 150))
const transferred = Math.round((total * percent) / 100)
const bytesPerSecond = Math.round((transferred - previousTransferred) / 0.15)
previousTransferred = transferred
appUpdateState = {
...appUpdateState,
status: 'downloading',
percent,
transferred,
total,
bytesPerSecond
}
for (const window of BrowserWindow.getAllWindows()) {
if (!window.isDestroyed()) window.webContents.send('app-update:state', appUpdateState)
}
}
appUpdateState = {
...appUpdateState,
status: 'downloaded',
percent: 100,
transferred: total,
total,
bytesPerSecond: undefined
}
for (const window of BrowserWindow.getAllWindows()) {
if (!window.isDestroyed()) window.webContents.send('app-update:state', appUpdateState)
}
return { success: true, state: appUpdateState }
})
handle('app-update:install', async () =>
updateSimulation
? {
success: true,
simulated: true,
message: '开发模拟模式:更新安装动作已模拟,未实际退出应用。'
}
: { success: true }
)
for (const channel of [
'export:start',
@@ -817,8 +895,6 @@ for (const channel of [
'image:selectDecoder',
'image:openDecoderDownload',
'app-update:check',
'app-update:download',
'app-update:install',
'agent-hub:clearLogs',
'agent-hub:startLogin',
'agent-hub:cancelLogin',
+4
View File
@@ -21,6 +21,8 @@ export async function launchTestApp(
largeContacts?: number
corruptCache?: boolean
aiFailure?: string
updateSimulation?: boolean
unsignedMacUpdate?: boolean
now?: number
appearanceTheme?: 'light' | 'dark'
stableUserData?: string
@@ -49,6 +51,8 @@ export async function launchTestApp(
WXE_E2E_LARGE_CONTACTS: String(options.largeContacts || 0),
WXE_E2E_CORRUPT_CACHE: options.corruptCache ? '1' : '0',
WXE_E2E_AI_FAILURE: options.aiFailure || '',
WXE_E2E_UPDATE_SIMULATION: options.updateSimulation ? '1' : '0',
WXE_E2E_UNSIGNED_MAC_UPDATE: options.unsignedMacUpdate ? '1' : '0',
WXE_E2E_NOW_MS: options.now ? String(options.now) : '',
WXE_E2E_APPEARANCE_THEME: options.appearanceTheme || 'light'
}
+66
View File
@@ -497,6 +497,72 @@ test('SETTINGS-02 basic settings controls visual @visual', async () => {
}
})
test('UPDATE-01 startup prompt and downloaded state visual @visual', async () => {
const fixture = await launchTestApp({ now: visualNow, updateSimulation: true })
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.0.0' })
await expect(dialog).toBeVisible()
await clearScreenshotFocus(fixture.page)
await expect(fixture.page).toHaveScreenshot('update-available-dialog.png', {
animations: 'disabled',
caret: 'hide'
})
await dialog.getByRole('button', { name: '立即下载' }).click()
await expect(fixture.page.getByText('v2.0.0 已准备完成')).toBeVisible({ timeout: 5_000 })
expect(
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
).toBe(true)
expect(pageErrors).toEqual([])
await clearScreenshotFocus(fixture.page)
await expect(fixture.page).toHaveScreenshot('settings-update-downloaded.png', {
animations: 'disabled',
caret: 'hide'
})
} finally {
await fixture.close()
}
})
test('UPDATE-02 unsigned macOS release prompt visual @visual', async () => {
const fixture = await launchTestApp({ now: visualNow, unsignedMacUpdate: true })
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.2.3' })
await expect(dialog).toBeVisible()
await clearScreenshotFocus(fixture.page)
await expect(fixture.page).toHaveScreenshot('update-release-page-dialog.png', {
animations: 'disabled',
caret: 'hide'
})
await dialog.getByRole('button', { name: '取消' }).click()
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
.click()
await fixture.page.getByRole('button', { name: '关于' }).click()
await expect(fixture.page.getByRole('button', { name: '前往下载' })).toBeVisible()
await expect(fixture.page.getByRole('progressbar')).toHaveCount(0)
expect(
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
).toBe(true)
expect(pageErrors).toEqual([])
await clearScreenshotFocus(fixture.page)
await expect(fixture.page).toHaveScreenshot('settings-update-release-page.png', {
animations: 'disabled',
caret: 'hide'
})
} finally {
await fixture.close()
}
})
test('SETTINGS-03 database key controls visual @visual', async () => {
const fixture = await launchTestApp({ now: visualNow })
const pageErrors: Error[] = []